12-7 ~ 12-19杂题

我已经是一条咸鱼了
随便写下两个星期来干的闲事

[USACO13JAN]Cow Lineup

Description

长度为$N(1 \le N \le 10^5)$的数字序列$(1 \le a_i \le 10^9)$,最多可删除$k$种数字。求同一种数字能够连续出现的最大长度。

Solution

猫说可以二分。
然后我就开始二分了。
直接二分答案,最大长度。但是要把相同的数字用链表穿起来。判断是否可行的时候,枚举作为答案的那个数字,然后看看是否存在某一段之间存在不同的数字小于等于$k$。
写法比较工业,主席树在线询问区间不同数字个数。
具体怎么用主席树写这个吗,下面有。
每次判断的复杂度均摊下来是$O(Nlog_2N)$的。其中扫数字$O(N)$,查询$O(log_2N)$。再加上二分所以总复杂度是$O(Nlog_2^2N)$。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <bits/stdc++.h>
using namespace std ;
void Read ( int &x, char c = getchar() ) {
for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
const int maxn = 1e5+5 ;
int n, m, k, a[maxn], b[maxn], size[maxn], Head[maxn], Next[maxn], Pre[maxn] ;
int tot, tree[maxn*60], ch[maxn*60][2], rt[maxn] ;
void push_up ( int x ) {
tree[x] = 0 ;
if (ch[x][0]) tree[x] += tree[ch[x][0]] ;
if (ch[x][1]) tree[x] += tree[ch[x][1]] ;
}
int create ( int l, int r, int v ) {
int x = ++tot, mid = (l+r)>>1 ;
if (l == r) {
++tree[x] ;
return x ;
}
if (v <= mid) ch[x][0] = create(l, mid, v) ;
else ch[x][1] = create(mid+1, r, v) ;
push_up(x) ;
return x ;
}
int insert ( int h, int l, int r, int v, int c ) {
int x = ++tot, mid = (l+r)>>1 ;
if (l == r) {
if (h) tree[x] = tree[h] ;
tree[x] += c ;
return x ;
}
if (v <= mid) {
ch[x][0] = insert(ch[h][0], l, mid, v, c) ;
if (h) ch[x][1] = ch[h][1] ;
} else {
ch[x][0] = ch[h][0] ;
if (h) ch[x][1] = insert(ch[h][1], mid+1, r, v, c) ;
}
push_up(x) ;
return x ;
}
int query ( int h, int l, int r, int x, int y ) {
if (x <= l && r <= y) return tree[h] ;
int mid = (l+r)>>1 ;
if (y <= mid) return query(ch[h][0], l, mid, x, y) ;
else if (x > mid) return query(ch[h][1], mid+1, r, x, y) ;
return query(ch[h][0], l, mid, x, mid)+query(ch[h][1], mid+1, r, mid+1, y) ;
}
bool calc ( int x, int value ) {
int rec, l, r ;
for ( r = l = Head[x] ; value > 1 ; r = Next[r], value -- ) ;
if (r == 0) return 0 ;
for ( ; l && r ; l = Next[l], r = Next[r] ) {
rec = query(rt[r], 1, n, l, r) ;
if (rec <= m+1) return 1 ;
}
return 0 ;
}
bool judge ( int value ) {
int i ;
for ( i = 1 ; i <= n ; i ++ )
if (size[i] >= value && calc(i, value)) return 1 ;
return 0 ;
}
int main() {
int i ;
Read(n) ; Read(m) ;
for ( i = 1 ; i <= n ; i ++ ) Read(a[i]), b[i] = a[i] ;
sort(b+1, b+n+1) ;
k = unique(b+1, b+n+1)-b-1 ;
for ( i = 1 ; i <= n ; i ++ )
++size[a[i] = lower_bound(b+1, b+k+1, a[i])-b] ;
for ( i = n ; i ; i -- )
if (Head[a[i]]) {
Pre[Head[a[i]]] = i ;
Next[i] = Head[a[i]], Head[a[i]] = i ;
}
else Head[a[i]] = i ;
rt[1] = create(1, n, 1) ;
for ( i = 2 ; i <= n ; i ++ ) {
if (Pre[i]) {
rt[i] = insert(rt[i - 1], 1, n, Pre[i], -1) ;
rt[i] = insert(rt[i], 1, n, i, 1) ;
} else rt[i] = insert(rt[i - 1], 1, n, i, 1) ;
}
int l, r, mid, rec = 0 ;
l = 1, r = n ;
while (l <= r) {
mid = (l+r)>>1 ;
if (judge(mid)) rec = mid, l = mid+1 ;
else r = mid-1 ;
}
printf ( "%d\n", rec ) ;
return 0 ;
}

[SDOI2009]HH的项链

Description

长度为$N(1 \le N \le 5 \times 10^4)$的序列,$Q(1 \le Q \le 2 \times 10^5)$次询问,每次询问区间$[L_i,R_i]$之间不同数字的个数。

Solution

离线可以用莫队,好久以前写过。
某天晚上闲着无聊,睡觉又太早,就写个板子娱乐一下。
在线做法主席树。
对每个点建立一棵线段树,线段树$i$维护${-1,0,1}$序列表示原序列的区间$[1,i]$ 上的值的贡献(${-1,0,1}$表示贡献)。
新加入数字$a_i$,若该数字以前在位置$pre[i]$出现过,那么线段树$i$就应该在其维护的序列上的位置$i$设为$1$,$pre[i]$设为$-1$。
例如,序列${1,5,2,4,3,5,6,7}$,其中第$5$棵线段树所维护的序列应该是${1,-1,1,1,1,1,0,0}$。
每次询问$[L,R]$,只需在第$R$棵线段树上查询$[L,R]$的和即可。
可持久化即可。
值得注意的是,每次加入一个数可能之前还要修改某个位置为$-1$,也就代表着可能新增$2$条链,即$log_2N$个点。注意数组。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <bits/stdc++.h>
using namespace std ;
void Read ( int &x, char c = getchar() ) {
for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
const int maxn = 5e4+5 ;
int n, m, a[maxn], b[maxn], pre[maxn] ;
int tree[maxn*60], ch[maxn*60][2], rt[maxn], tot ;
void push_up ( int x ) {
tree[x] = 0 ;
if (ch[x][0]) tree[x] += tree[ch[x][0]] ;
if (ch[x][1]) tree[x] += tree[ch[x][1]] ;
}
int create ( int l, int r, int v ) {
int x = ++tot, mid = (l+r)>>1 ;
if (l == r) {
tree[x] ++ ;
return x ;
}
if (v <= mid) ch[x][0] = create(l, mid, v) ;
else ch[x][1] = create(mid+1, r, v) ;
push_up(x) ;
return x ;
}
int insert ( int h, int l, int r, int v, int value ) {
int x = ++tot, mid = (l+r)>>1 ;
if (l == r) {
tree[x] = tree[h] ;
tree[x] += value ;
return x ;
}
if (v <= mid) {
ch[x][0] = insert(ch[h][0], l, mid, v, value) ;
ch[x][1] = ch[h][1] ;
} else {
ch[x][0] = ch[h][0] ;
ch[x][1] = insert(ch[h][1], mid+1, r, v, value) ;
}
push_up(x) ;
return x ;
}
int query ( int h, int l, int r, int x, int y ) {
if (h == 0) return 0 ;
if (x <= l && r <= y) return tree[h] ;
int mid = (l+r)>>1 ;
if (y <= mid) return query(ch[h][0], l, mid, x, y) ;
else if (x > mid) return query(ch[h][1], mid+1, r, x, y) ;
return query(ch[h][0], l, mid, x, mid)+query(ch[h][1], mid+1, r, mid+1, y) ;
}
void check ( int x, int l, int r ) {
printf ( "%d [%d,%d]=%d\n", x, l, r, tree[x] ) ;
int mid = (l+r)>>1 ;
if (ch[x][0]) check(ch[x][0], l, mid) ;
if (ch[x][1]) check(ch[x][1], mid+1, r) ;
}
int main() {
int i, l, r, _ ;
Read(n) ;
for ( i = 1 ; i <= n ; i ++ ) Read(a[i]), b[i] = a[i] ;
sort(b+1, b+n+1) ;
m = unique(b+1, b+n+1)-b-1 ;
for ( i = 1 ; i <= n ; i ++ )
a[i] = lower_bound(b+1, b+m+1, a[i])-b ;
memset ( b, 0, sizeof b ) ;
for ( i = 1 ; i <= n ; i ++ )
pre[i] = b[a[i]], b[a[i]] = i ;
rt[1] = create(1, n, 1) ;
for ( i = 2 ; i <= n ; i ++ ) {
if (pre[i]) {
rt[i] = insert(rt[i - 1], 1, n, pre[i], -1) ;
rt[i] = insert(rt[i], 1, n, i, 1) ;
} else rt[i] = insert(rt[i - 1], 1, n, i, 1) ;
}
Read(_) ;
while (_--) {
Read(l) ; Read(r) ;
printf ( "%d\n", query(rt[r], 1, n, l, r) ) ;
}
return 0 ;
}

[SCOI2008]奖励关

Description

复制下来占地方
$k \le 100, N \le 15$

Solution

本来不会做,看到数据范围就会了。
状压DP。
$f[i][x]$代表当前是第$i$轮,已经获取过的宝物集合为$x$。
每一轮枚一下出来宝物的种类,状态为$O(K \times 2^N)$,转移为$O(N)$。总复杂度是$O(K \times N \times 2^N)$。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
using namespace std ;
int n, m, c[105], p[105] ;
double f[105][(1<<15)+5] ;
int main() {
int i, j, k, s, S ;
scanf ( "%d%d", &m, &n ) ;
S = (1<<n)-1 ;
for ( i = 1 ; i <= n ; i ++ ) {
scanf ( "%d", c+i ) ;
while (scanf("%d",&k) && k)
p[i] |= 1<<(k-1) ;
}
for ( i = m-1 ; i >= 0 ; i -- ) {
for ( s = 0 ; s <= S ; s ++ )
for ( j = 1 ; j <= n ; j ++ )
if ( (p[j]&s) == p[j] )
f[i][s] += max(f[i + 1][s]/n, (f[i + 1][s|(1<<(j-1))]+c[j])*1.0/n) ;
else f[i][s] += f[i + 1][s]/n ;
}
printf ( "%.6lf\n", f[0][0] ) ;
return 0 ;
}

[AHOI2009]中国象棋

Description

在一个$N$行$M$列的棋盘上,让你放若干个炮(可以是$0$个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法,模$9999973$。$N,M \le 100$

Solution

此题巧妙无比,看了题解才会做。
套路啊。
反正每一行每一列放的数量只可能是${0,1,2}$。
$f[i][a][b]$代表前$i$行,有$a$列放了$0$个炮,有$b$列放了$1$个炮,那么有$M-a-b$列放了$2$个炮。
这一行可以不放,也可以放$1$个。也可以放$2$个。
分类讨论统计答案即可。
这种状态定的简直妙啊。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <bits/stdc++.h>
#define LL long long
using namespace std ;
const int maxn = 105, modd = 9999973 ;
int n, m ;
LL f[maxn][maxn][maxn] ;
LL c ( int x ) { return ((LL)x*(x-1)/2)%modd ; }
int main() {
int i, a, b ;
scanf ( "%d%d", &n, &m ) ;
f[0][m][0] = 1 ;
for ( i = 0 ; i < n ; i ++ ) {
for ( a = 0 ; a <= m ; a ++ )
for ( b = 0 ; a+b <= m ; b ++ ) {
(f[i + 1][a][b] += f[i][a][b]) %= modd ;
if (a >= 1) (f[i + 1][a - 1][b + 1] += a*f[i][a][b]%modd) %= modd ;
if (b >= 1) (f[i + 1][a][b - 1] += b*f[i][a][b]%modd) %= modd ;
if (a >= 1) (f[i + 1][a - 1][b] += a*b*f[i][a][b]%modd) %= modd ;
if (a >= 2) (f[i + 1][a - 2][b + 2] += c(a)*f[i][a][b]%modd) %= modd ;
if (b >= 2) (f[i + 1][a][b - 2] += c(b)*f[i][a][b]%modd) %= modd ;
}
}
LL ans = 0 ;
for ( a = 0 ; a <= m ; a ++ )
for ( b = 0 ; a+b <= m ; b ++ )
(ans += f[n][a][b]) %= modd ;
printf ( "%lld\n", ans ) ;
return 0 ;
}

[SCOI2005]最大子矩阵

Description

这里有一个$n\times m$的矩阵,请你选出其中$k$个子矩阵,使得这个$k$个子矩阵分值之和最大。注意:选出的$k$个子矩阵不能相互重叠。$1 \le n \le 100,1 \le m \le 2,1 \le k \le 10$。

Solution

对$m$分情况讨论。
$m=1$。暴力DP。随便搞。

$m=2$。
先记录三个前缀和,单独第1列,单独第2列,两列加起来。
$f[a][b][t]$表示第1列最后一个选取的位置是a,第2列最后一个选取的位置是b,一共选了t个矩形。讨论当前新选一个矩形是完全处于第1列还是完全处于第2列还是宽为2。
DP转移的时候枚举前一个矩形的结尾,当前矩形的开头用RMQ在前缀和上查询。
状态为$O(n^2 \times k)$,转移为$O(n)$,总复杂度为$O(n^3 \times k)$。
网上别人的做法貌似是$O(nk)$的,我还是太辣鸡了。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <bits/stdc++.h>
using namespace std ;
const int maxn = 105, zhf = 1e7 ;
int n, m, k, a[maxn][3], g[maxn][10][3], f[maxn][maxn][30], dp[maxn][30] ;
void init ( int t ) {
int i, j ;
for ( i = 1 ; i <= n ; i ++ )
g[i][0][t] = (a[i][t] += a[i - 1][t]) ;
for ( j = 1 ; j < 10 ; j ++ )
for ( i = 1 ; i+(1<<j)-1 <= n ; i ++ )
g[i][j][t] = min(g[i][j-1][t], g[i+(1<<(j-1))][j-1][t]) ;
}
int query ( int l, int r, int t ) {
if (l > r) return zhf ;
int len = floor(log(r-l+1)/log(2.0)) ;
return min(g[l][len][t], g[r-(1<<len)+1][len][t]) ;
}
int check_max ( int &x, int y ) { return x = x>y ? x:y ; }
void solve1() {
int i, j, l ;
for ( i = 1 ; i <= n ; i ++ )
scanf ( "%d", &a[i][0] ) ;
init(0) ;
for ( i = 1 ; i <= n ; i ++ ) {
for ( j = 0 ; j < i ; j ++ )
for ( l = 1 ; l <= k ; l ++ ) {
check_max(dp[i][l], dp[j][l-1] + a[i][0] - query(j,i-1,0)) ;
}
}
int ans = 0 ;
for ( i = 1 ; i <= n ; i ++ )
for ( j = 1 ; j <= k ; j ++ )
check_max(ans, dp[i][j] ) ;
printf ( "%d\n", ans ) ;
}
void solve2() {
register int ans = 0, i, j, l, r, t ;
for ( i = 1 ; i <= n ; i ++ ) {
scanf ( "%d%d", &a[i][0], &a[i][1] ) ;
a[i][2] = a[i][0] + a[i][1] ;
}
init(0) ; init(1) ; init(2) ;
for ( i = 0 ; i <= n ; i ++ )
for ( j = 0 ; j <= n ; j ++ ) {
check_max(f[i][j][(i>=1)+(j>=1)], a[i][0]+a[j][1]) ;
for ( t = 1 ; t <= k ; t ++ ) {
for ( l = 0 ; l < i ; l ++ )
check_max(f[i][j][t], f[l][j][t-1] + a[i][0] - query(l,i-1,0)) ;
for ( l = 0 ; l < j ; l ++ )
check_max(f[i][j][t], f[i][l][t-1] + a[j][1] - query(l,j-1,1)) ;
if (i == j && i) {
for ( l = 0 ; l < i ; l ++ )
for ( r = 0 ; r < i ; r ++ )
check_max(f[i][j][t], f[l][r][t-1] + a[i][2] - query(max(l,r),i-1,2)) ;
}
check_max(ans, f[i][j][t]) ;
}
}
printf ( "%d\n", ans ) ;
}
int main() {
scanf ( "%d%d%d", &n, &m, &k ) ;
if (m == 1) solve1() ;
else solve2() ;
return 0 ;
}

[SCOI2005]互不侵犯King

Description

在$N \times N$的棋盘里面放$K$个国王,使他们互不攻击,共有多少种摆放方案。$N \le 9$

Solution

本来不会做,看了范围才会。
状压DP,转移时枚举上一行状态。
时间复杂度$O(N \times 2^{2N})$。
然后也是某个晚上闲着无聊,睡觉又太早就娱乐了一波。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
#define LL long long
using namespace std ;
LL n, m, f[10][520][100], bt[520] ;
bool judge ( LL a, LL b ) {
if (a&b) return 0 ;
a |= b ;
if (a&(a>>1)) return 0 ;
return 1 ;
}
int main() {
LL i, j, s, x, S ;
cin >> n >> m ;
S = (1<<n)-1 ;
for ( s = 0 ; s <= S ; s ++ )
bt[s] = bt[s>>1] + (s&1) ;
memset ( f, 0, sizeof f ) ;
for ( s = 0 ; s <= S ; s ++ )
if (judge(0, s)) f[1][s][bt[s]] = 1 ;
for ( i = 2 ; i <= n ; i ++ )
for ( s = 0 ; s <= S ; s ++ )
for ( x = 0 ; x <= S ; x ++ )
if (judge(s, x)) {
for ( j = bt[s]+bt[x] ; j <= m ; j ++ )
if (f[i - 1][x][j - bt[s]] > 0) f[i][s][j] += f[i - 1][x][j - bt[s]] ;
}
LL ans = 0 ;
for ( s = 0 ; s <= S ; s ++ )
if (f[n][s][m] > 0) ans += f[n][s][m] ;
printf ( "%lld\n", ans ) ;
return 0 ;
}

[USACO5.1]Musical Themes

Description

转化一下题意之后,求最长重复子串。$N \le 5000$

Solution

链一波易大佬的DP做法
然而我太菜了不会DP,只好用后缀数组的套路来做。
二分答案,对height分组。都是套路没什么好说的。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <bits/stdc++.h>
using namespace std ;
const int maxn = 2e5+5 ;
int rnk[maxn], s[maxn], n, m, sa[maxn], height[maxn], s1[maxn], s2[maxn], c[maxn], a[maxn] ;
void get_sa() {
int i, p, len ;
int *x = s1, *y = s2 ;
for ( i = 1 ; i <= n ; i ++ ) m = max(m, s[i]) ;
memset ( c, 0, sizeof c ) ;
for ( i = 1 ; i <= n ; i ++ ) c[x[i] = s[i]] ++ ;
for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
for ( i = n ; i ; i -- ) sa[c[x[i]]--] = i ;
for ( len = 1 ; len < n ; len <<= 1, m = p ) {
p = 0 ;
for ( i = n-len+1 ; i <= n ; i ++ ) y[++p] = i ;
for ( i = 1 ; i <= n ; i ++ )
if (sa[i] > len) y[++p] = sa[i]-len ;
memset ( c, 0, sizeof c ) ;
for ( i = 1 ; i <= n ; i ++ ) c[x[y[i]]] ++ ;
for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
for ( i = n ; i ; i -- ) sa[c[x[y[i]]]--] = y[i] ;
swap(x, y) ;
p = 1 ;
x[sa[1]] = 1 ;
for ( i = 2 ; i <= n ; i ++ )
if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+len]==y[sa[i-1]+len])
x[sa[i]] = p ;
else x[sa[i]] = ++p ;
if (p >= n) break ;
}
}
void get_height() {
int i, j, k = 0 ;
for ( i = 1 ; i <= n ; i ++ ) rnk[sa[i]] = i ;
for ( i = 1 ; i <= n ; i ++ ) {
if (k) -- k ;
j = sa[rnk[i]-1] ;
while (s[i+k] == s[j+k]) ++ k ;
height[rnk[i]] = k ;
}
}
bool judge ( int len ) {
int i, minn = sa[1], maxx = sa[1] ;
for ( i = 2 ; i <= n ; i ++ )
if (height[i] < len) minn = maxx = sa[i] ;
else {
minn = min(minn, sa[i]) ;
maxx = max(maxx, sa[i]) ;
if (maxx - minn > len) return 1 ;
}
return 0 ;
}
int main() {
int i, l, r, mid, rec ;
scanf ( "%d", &n ) ;
for ( i = 1 ; i <= n ; i ++ ) scanf ( "%d", a+i ) ;
for ( i = 1 ; i <= n ; i ++ ) s[i] = a[i] - a[i - 1] + 100 ;
get_sa() ;
get_height() ;
l = 1, r = n>>1 ;
while (l <= r) {
mid = (l+r)>>1 ;
if (judge(mid)) rec = mid, l = mid+1 ;
else r = mid-1 ;
}
if (rec < 4) rec = 0 ;
else ++ rec ;
printf ( "%d\n", rec ) ;
return 0 ;
}

[HAOI2008]硬币购物

Description

硬币购物一共有$4$种硬币。面值分别为$c_1$,$c_2$,$c_3$,$c_4$。某人去商店买东西,去了$tot$次。每次带$d_i$枚$c_i$硬币,买$s_i$的价值的东西。请问每次有多少种付款方法。$d_i,s \le 100000, tot \le 1000$。

Solution

要是没有硬币的个数限制就背包。
具体叫什么名字忘记了。
加上了硬币个数,就把不合理的强行减去。容斥。
蛇皮操作,写法奇丑无比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
#define LL long long
using namespace std ;
void Read ( LL &x, char c = getchar() ) {
for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
LL n, m, v[5] ;
LL f[100005] ;
void calc ( LL &rec, LL x, LL value ) {
if (x >= 0) rec += f[x]*value ;
}
int main() {
LL i, j, t, rec, a, b, c, d ;
for ( i = 1 ; i < 5 ; i ++ ) Read(v[i]) ;
Read(m) ;
n = 1e5 ;
f[0] = 1 ;
for ( i = 1 ; i < 5 ; i ++ )
for ( j = v[i] ; j <= n ; j ++ )
f[j] += f[j - v[i]] ;
for ( i = 1 ; i <= m ; i ++ ) {
rec = 0 ;
Read(a) ; Read(b) ; Read(c) ; Read(d) ; Read(t) ;
++ a ; ++ b ; ++ c ; ++ d ;
calc(rec, t, 1) ;
calc(rec, t - a*v[1], -1) ;
calc(rec, t - b*v[2], -1) ;
calc(rec, t - c*v[3], -1) ;
calc(rec, t - d*v[4], -1) ;
calc(rec, t - a*v[1] - b*v[2], 1) ;
calc(rec, t - a*v[1] - c*v[3], 1) ;
calc(rec, t - a*v[1] - d*v[4], 1) ;
calc(rec, t - b*v[2] - c*v[3], 1) ;
calc(rec, t - b*v[2] - d*v[4], 1) ;
calc(rec, t - c*v[3] - d*v[4], 1) ;
calc(rec, t - a*v[1] - b*v[2] - c*v[3], -1) ;
calc(rec, t - a*v[1] - b*v[2] - d*v[4], -1) ;
calc(rec, t - a*v[1] - c*v[3] - d*v[4], -1) ;
calc(rec, t - b*v[2] - c*v[3] - d*v[4], -1) ;
calc(rec, t - a*v[1] - b*v[2] - c*v[3] - d*v[4], 1) ;
printf ( "%lld\n", rec ) ;
}
return 0 ;
}

[USACO06DEC]Milk Patterns

Description

农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。

Solution

后缀数组的套路题,二分答案,计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <bits/stdc++.h>
using namespace std ;
void Read ( int &x, char c = getchar() ) {
for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
const int maxn = 2e4+5 ;
int n, m, e, s[maxn], b[maxn], sa[maxn], height[maxn], s1[maxn], s2[maxn], rnk[maxn], c[maxn] ;
void init() {
int i, p, len ;
int *x = s1, *y = s2 ;
memset ( c, 0, sizeof c ) ;
for ( i = 1 ; i <= n ; i ++ ) c[x[i] = s[i]] ++ ;
for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
for ( i = n ; i ; i -- ) sa[c[x[i]]--] = i ;
for ( len = 1 ; len < n ; len <<= 1, m = p ) {
p = 0 ;
for ( i = n-len+1 ; i <= n ; i ++ ) y[++p] = i ;
for ( i = 1 ; i <= n ; i ++ )
if (sa[i] > len) y[++p] = sa[i]-len ;
memset ( c, 0, sizeof c ) ;
for ( i = 1 ; i <= n ; i ++ ) c[x[y[i]]] ++ ;
for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
for ( i = n ; i ; i -- ) sa[c[x[y[i]]]--] = y[i] ;
swap(x, y) ;
x[sa[1]] = p = 1 ;
for ( i = 2 ; i <= n ; i ++ )
if (y[sa[i]] == y[sa[i-1]] && y[sa[i]+len] == y[sa[i-1]+len])
x[sa[i]] = p ;
else x[sa[i]] = ++p ;
if (p >= n) break ;
}
for ( i = 1 ; i <= n ; i ++ ) rnk[sa[i]] = i ;
len = 0 ;
for ( i = 1 ; i <= n ; i ++ ) {
if (len) len-- ;
p = sa[rnk[i]-1] ;
while (s[i+len] == s[p+len]) ++ len ;
height[rnk[i]] = len ;
}
}
bool judge ( int v ) {
int i, ans = 0, rec ;
for ( i = 1 ; i <= n ; i ++ )
if (height[i] < v) rec = 1 ;
else ans = max(ans, ++rec) ;
return ans >= e ;
}
int main() {
int i, l, r, mid, rec ;
Read(n) ; Read(e) ;
for ( i = 1 ; i <= n ; i ++ ) Read(s[i]), b[i] = s[i] ;
sort(b+1, b+n+1) ;
m = unique(b+1, b+n+1)-b-1 ;
for ( i = 1 ; i <= n ; i ++ )
s[i] = lower_bound(b+1, b+m+1, s[i])-b ;
init() ;
l = 1, r = n ;
while (l <= r) {
mid = (l+r)>>1 ;
if (judge(mid)) rec = mid, l = mid+1 ;
else r = mid-1 ;
}
printf ( "%d\n", rec ) ;
return 0 ;
}

[HAOI2012]高速公路

Description

Y901高速公路是一条由$N-1$段路以及$N$个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为$1$~$N$,从收费站$i$行驶到$i+1$或从$i+1$行驶到$i$需要收取$V_i$的费用。高速路刚建成时所有的路段都是免费的。

政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或降价。

无聊的小A同学总喜欢研究一些稀奇古怪的问题,他开车在这条高速路上行驶时想到了这样一个问题:对于给定的$l,r(l \lt r)$,在第$l$个到第$r$个收费站里等概率随机取出两个不同的收费站$a$和$b$,那么从$a$行驶到$b$将期望花费多少费用呢?

所有C操作中的$v$的绝对值不超过$10000$
在任何时刻任意道路的费用均为不超过$10000$的非负整数
$N, M \le 10^5$

Solution

显然我们只要考虑道路。
设道路$i$连接点$i$和点$i+1$。
对于道路$i$,被选到的概率应该是$l$在$i$左边,$r$在$i+1$右边的概率。
推下式子。

所以,维护三个值$\sum V_i$,$\sum V_i \times i$和$\sum V_i \times i^2$。
不用像网上大多数题解说的维护$\sum i^2$和$\sum i$,修改的时候利用公式做前缀差分就行了。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <bits/stdc++.h>
#define LL long long
using namespace std ;
void Read ( LL &x, char c = getchar(), bool f = 0 ) {
for ( x = 0 ; !isdigit(c) ; c = getchar() ) if (c == '-') f = 1 ;
for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
if (f) x = -x ;
}
LL gcd ( LL a, LL b ) { return b?gcd(b,a%b):a; }
const int maxn = 1e5+5 ;
LL sum1 ( int x ) { return (LL)x*(x+1)/2 ; }
LL sum2 ( int x ) { return (LL)x*(x+1)*(2*x+1)/6 ; }
LL n, m, tree[maxn*3][3], tag[maxn*3] ;
void push_up ( int h ) {
tree[h][0] = tree[h<<1][0]+tree[h<<1|1][0] ;
tree[h][1] = tree[h<<1][1]+tree[h<<1|1][1] ;
tree[h][2] = tree[h<<1][2]+tree[h<<1|1][2] ;
}
void push_down ( int h, int l, int r ) {
int mid = (l+r)>>1 ;
if (tag[h]) {
tag[h<<1] += tag[h] ;
tag[h<<1|1] += tag[h] ;
tree[h<<1][0] += tag[h]*(mid-l+1) ;
tree[h<<1|1][0] += tag[h]*(r-mid) ;
tree[h<<1][1] += tag[h]*(sum1(mid)-sum1(l-1)) ;
tree[h<<1|1][1] += tag[h]*(sum1(r)-sum1(mid)) ;
tree[h<<1][2] += tag[h]*(sum2(mid)-sum2(l-1)) ;
tree[h<<1|1][2] += tag[h]*(sum2(r)-sum2(mid)) ;
tag[h] = 0 ;
}
}
void update ( int h, int l, int r, int x, int y, LL v ) {
if (x <= l && r <= y) {
tag[h] += v ;
tree[h][0] += (r-l+1)*v ;
tree[h][1] += v*(sum1(r)-sum1(l-1)) ;
tree[h][2] += v*(sum2(r)-sum2(l-1)) ;
return ;
}
push_down(h, l, r) ;
int mid = (l+r)>>1 ;
if (y <= mid) update(h<<1, l, mid, x, y, v) ;
else if (x > mid) update(h<<1|1, mid+1, r, x, y, v) ;
else {
update(h<<1, l, mid, x, mid, v) ;
update(h<<1|1, mid+1, r, mid+1, y, v) ;
}
push_up(h) ;
}
LL query ( int h, int l, int r, int x, int y, int v ) {
if (x <= l && r <= y) return tree[h][v] ;
push_down(h, l, r) ;
int mid = (l+r)>>1 ;
if (y <= mid) return query(h<<1, l, mid, x, y, v) ;
else if (x > mid) return query(h<<1|1, mid+1, r, x, y, v) ;
return query(h<<1, l, mid, x, mid, v)+query(h<<1|1, mid+1, r, mid+1, y, v) ;
}
char cmd[5] ;
int main() {
LL l, r, v, rec0, rec1, rec2, rec, d ;
Read(n) ; Read(m) ;
while(m--) {
scanf ( "%s", cmd ) ;
Read(l) ; Read(r) ;
if (cmd[0] == 'C') {
Read(v) ;
update(1, 1, n, l, r-1, v) ;
} else {
rec0 = query(1, 1, n, l, r-1, 0) ;
rec1 = query(1, 1, n, l, r-1, 1) ;
rec2 = query(1, 1, n, l, r-1, 2) ;
rec = -rec2 + rec1*(r+l-1) + rec0*(r-l*r) ;
v = (r-l+1)*(r-l) ;
v >>= 1 ;
d = gcd(rec, v) ;
rec /= d ;
v /= d ;
printf ( "%lld/%lld\n", rec, v ) ;
}
}
return 0 ;
}

[SDOI2011]计算器

练了下快速幂和扩展欧几里得,学了下BSGS求离散对数。没什么好讲的。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <bits/stdc++.h>
#define LL long long
using namespace std ;
LL Qpow ( LL a, LL b, LL modd ) {
LL rec = 1 ;
for ( a %= modd; b ; b >>= 1, (a *= a) %= modd )
if (b&1) (rec *= a) %= modd ;
return rec ;
}
LL exgcd ( LL a, LL b, LL &x, LL &y ) {
if (b == 0) {
x = 1, y = 0 ;
return a ;
}
LL d = exgcd(b, a%b, y, x) ;
y -= a/b*x ;
return d ;
}
void solve1() {
LL a, b, c ;
scanf ( "%lld%lld%lld", &a, &b, &c ) ;
printf ( "%lld\n", Qpow(a, b, c) ) ;
}
void solve2() {
LL a, b, c, d, x, y ;
scanf ( "%lld%lld%lld", &a, &c, &b ) ;
d = exgcd(a, b, x, y) ;
if (c%d != 0) {
puts("Orz, I cannot find x!") ;
return ;
}
x = (x+b)%b ;
c /= d ;
x *= c ;
printf ( "%lld\n", x%b ) ;
}
map <LL, LL> g ;
void solve3() {
g.clear() ;
LL a, b, c, rec, i, m ;
scanf ( "%lld%lld%lld", &a, &b, &c ) ;
if (a%c == 0) goto end ;
m = ceil(sqrt(c)) ;
rec = b%c ;
for ( i = 0 ; i <= m ; i ++, (rec *= a) %= c )
if (!g[rec]) g[rec] = i ;
for ( i = 1 ; i <= m ; i ++ ) {
rec = Qpow(a, i*m, c) ;
if (g.count(rec)) {
rec = i*m - g[rec] ;
printf ( "%lld\n", (rec%c+c)%c ) ;
return ;
}
}
end:puts("Orz, I cannot find x!") ;
}
int main() {
int _, type ;
scanf ( "%d%d", &_, &type ) ;
while (_--) {
if (type == 1) solve1() ;
else if (type == 2) solve2() ;
else solve3() ;
}
return 0 ;
}

[SDOI2013]随机数生成器

Description

题面占地方。

Solution

设$v$次之后到达。发现展开之后是个多项式。

后一项用等比数列化简,第一项再通分后合并次数为$v-1$的项。求逆元后转化为离散对数。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <bits/stdc++.h>
#define LL long long
using namespace std ;
LL modd ;
LL Qpow ( LL a, LL b ) {
LL rec = 1 ;
for ( a %= modd ; b ; b >>= 1, (a *= a) %= modd )
if (b&1) (rec *= a) %= modd ;
return rec ;
}
map <LL, LL> Map ;
LL BSGS ( LL a, LL b ) {
if (b == 1) return 1 ;
LL i, t, m = ceil(sqrt(modd)), rec = b ;
Map.clear() ;
for ( i = 1 ; i <= m ; i ++ ) {
(rec *= a) %= modd ;
if (!Map.count(rec)) Map[rec] = i ;
}
t = Qpow(a, m) ;
rec = 1 ;
for ( i = 1 ; i <= m ; i ++ ) {
(rec *= t) %= modd ;
if (Map.count(rec)) return i*m - Map[rec] + 1 ;
}
return -1 ;
}
int main() {
LL _, a, b, x, t ;
cin >> _ ;
while (_--) {
cin >> modd >> a >> b >> x >> t ;
if (x == t) {
puts("1") ;
continue ;
}
if (a == 0) {
if (b == t) puts("2") ;
else puts("-1") ;
continue ;
}
if (a == 1 && b == 0) {
puts("-1") ;
continue ;
}
if (a == 1) {
cout << ((t-x+modd)%modd)*Qpow(b, modd-2)%modd+1 << endl ;
continue ;
}
t = (a*t%modd - t + modd + b)%modd ;
(t *= Qpow(a*x%modd - x + b + modd, modd-2)) %= modd ;
cout << BSGS(a, t) << endl ;
}
return 0 ;
}

未完留坑