我已经是一条咸鱼了
随便写下两个星期来干的闲事
长度为 N(1≤N≤105) 的数字序列 (1≤ai≤109) ,最多可删除 k 种数字。求同一种数字能够连续出现的最大长度。
猫说可以二分。
然后我就开始二分了。
直接二分答案,最大长度。但是要把相同的数字用链表穿起来。判断是否可行的时候,枚举作为答案的那个数字,然后看看是否存在某一段之间存在不同的数字小于等于 k 。
写法比较工业,主席树在线询问区间不同数字个数。
具体怎么用主席树写这个吗,下面有。
每次判断的复杂度均摊下来是 O(Nlog2N) 的。其中扫数字 O(N) ,查询 O(log2N) 。再加上二分所以总复杂度是 O(Nlog22N) 。
#include
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 ;
}
长度为 N(1≤N≤5×104) 的序列, Q(1≤Q≤2×105) 次询问,每次询问区间 [Li,Ri] 之间不同数字的个数。
离线可以用莫队,好久以前写过。
某天晚上闲着无聊,睡觉又太早,就写个板子娱乐一下。
在线做法主席树。
对每个点建立一棵线段树,线段树 i 维护 {−1,0,1} 序列表示原序列的区间 [1,i] 上的值的贡献( {−1,0,1} 表示贡献)。
新加入数字 ai ,若该数字以前在位置 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 条链,即 log2N 个点。注意数组。
#include
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 ;
}
复制下来占地方
k≤100,N≤15
本来不会做,看到数据范围就会了。
状压DP。
f[i][x] 代表当前是第 i 轮,已经获取过的宝物集合为 x 。
每一轮枚一下出来宝物的种类,状态为 O(K×2N) ,转移为 O(N) 。总复杂度是 O(K×N×2N) 。
#include
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<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 ;
}
在一个 N 行 M 列的棋盘上,让你放若干个炮(可以是 0 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法,模 9999973 。 N,M≤100
此题巧妙无比,看了题解才会做。
套路啊。
反正每一行每一列放的数量只可能是 {0,1,2} 。
f[i][a][b] 代表前 i 行,有 a 列放了 0 个炮,有 b 列放了 1 个炮,那么有 M−a−b 列放了 2 个炮。
这一行可以不放,也可以放 1 个。也可以放 2 个。
分类讨论统计答案即可。
这种状态定的简直妙啊。
#include
#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 ;
}
这里有一个 n×m 的矩阵,请你选出其中 k 个子矩阵,使得这个 k 个子矩阵分值之和最大。注意:选出的 k 个子矩阵不能相互重叠。 1≤n≤100,1≤m≤2,1≤k≤10 。
对 m 分情况讨论。
m=1 。暴力DP。随便搞。
m=2 。
先记录三个前缀和,单独第1列,单独第2列,两列加起来。
f[a][b][t] 表示第1列最后一个选取的位置是a,第2列最后一个选取的位置是b,一共选了t个矩形。讨论当前新选一个矩形是完全处于第1列还是完全处于第2列还是宽为2。
DP转移的时候枚举前一个矩形的结尾,当前矩形的开头用RMQ在前缀和上查询。
状态为 O(n2×k) ,转移为 O(n) ,总复杂度为 O(n3×k) 。
网上别人的做法貌似是 O(nk) 的,我还是太辣鸡了。
#include
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<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<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 ;
}
在 N×N 的棋盘里面放 K 个国王,使他们互不攻击,共有多少种摆放方案。 N≤9
本来不会做,看了范围才会。
状压DP,转移时枚举上一行状态。
时间复杂度 O(N×22N) 。
然后也是某个晚上闲着无聊,睡觉又太早就娱乐了一波。
#include
#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<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 ;
}
转化一下题意之后,求最长重复子串。 N≤5000
链一波易大佬的DP做法。
然而我太菜了不会DP,只好用后缀数组的套路来做。
二分答案,对height分组。都是套路没什么好说的。
#include
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 ;
}
硬币购物一共有 4 种硬币。面值分别为 c1 , c2 , c3 , c4 。某人去商店买东西,去了 tot 次。每次带 di 枚 ci 硬币,买 si 的价值的东西。请问每次有多少种付款方法。 di,s≤100000,tot≤1000 。
要是没有硬币的个数限制就背包。
具体叫什么名字忘记了。
加上了硬币个数,就把不合理的强行减去。容斥。
蛇皮操作,写法奇丑无比。
#include
#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 ;
}
农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。
后缀数组的套路题,二分答案,计数。
#include
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 ;
}
Y901高速公路是一条由 N−1 段路以及 N 个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为 1 ~ N ,从收费站 i 行驶到 i+1 或从 i+1 行驶到 i 需要收取 Vi 的费用。高速路刚建成时所有的路段都是免费的。
政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或降价。
无聊的小A同学总喜欢研究一些稀奇古怪的问题,他开车在这条高速路上行驶时想到了这样一个问题:对于给定的 l,r(l<r) ,在第 l 个到第 r 个收费站里等概率随机取出两个不同的收费站 a 和 b ,那么从 a 行驶到 b 将期望花费多少费用呢?
所有C操作中的 v 的绝对值不超过 10000
在任何时刻任意道路的费用均为不超过 10000 的非负整数
N,M≤105
显然我们只要考虑道路。
设道路 i 连接点 i 和点 i+1 。
对于道路 i ,被选到的概率应该是 l 在 i 左边, r 在 i+1 右边的概率。
推下式子。
#include
#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 ;
}
练了下快速幂和扩展欧几里得,学了下BSGS求离散对数。没什么好讲的。
#include
#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 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 ;
}
题面占地方。
设 v 次之后到达。发现展开之后是个多项式。
#include
#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 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 ;
}