令
u
=
g
c
d
(
a
,
b
)
u = gcd(a, b)
u=gcd(a,b),设
l
c
m
(
a
,
b
)
=
k
∗
u
lcm(a, b) = k * u
lcm(a,b)=k∗u,则原式可写为
u
∗
(
k
∗
c
−
d
)
=
x
u * (k * c - d) = x
u∗(k∗c−d)=x,那么有整数解,必须要满足
u
∣
x
u | x
u∣x. 则我们只需要枚举
x
x
x 的约数,这样子,就可以求出
k
k
k 了。
k
=
x
/
u
+
d
c
k = \frac{x/u+d}{c}
k=cx/u+d. 那么,我们确定了
g
c
d
gcd
gcd 与
l
c
m
lcm
lcm,这样子的话,
(
a
,
b
)
(a,b)
(a,b) 的组合数就是
2
l
c
m
/
g
c
d
的
质
因
数
个
数
2^{lcm / gcd的质因数个数}
2lcm/gcd的质因数个数.
那么我们先晒出来每个数质因数的个数即可,这个地方还挺容易错的,就是在计算
s
t
[
i
∗
p
r
i
m
e
[
j
]
]
st[i * prime[j]]
st[i∗prime[j]] 的地方的时候,要分
i
%
p
r
i
m
e
[
j
]
=
=
0
i \% prime[j] == 0
i%prime[j]==0 讨论。因为每一个质数只能算入一次。
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;constint N =20000010;int prime[N], cnt;int st[N];
ll pw[100]={1};voidsieve(int n){for(int i =2; i <= n; i++){if(!st[i]){
prime[cnt++]= i;
st[i]=1;}for(int j =0; prime[j]<= n / i; j++){if(i % prime[j]==0) st[i * prime[j]]= st[i];else st[i * prime[j]]= st[i]+1;if(i % prime[j]==0)break;}}for(int i =1; i <30; i++) pw[i]= pw[i -1]*2;}
vector<int>divisor(ll n){
vector<int> res;for(int i =1; i <= n / i; i++){if(n % i ==0){
res.push_back(i);if(n / i != i) res.push_back(n / i);}}return res;}intmain(){sieve(N -1);int T;scanf("%d",&T);while(T--){
ll ans =0;
ll a, b, c;scanf("%lld%lld%lld",&a,&b,&c);
vector<int> res =divisor(c);for(auto p : res){
ll tmp = c / p + b;if(tmp % a)continue;
ll k = tmp / a;
ans += pw[st[k]];}printf("%lld\n", ans);}return0;}
C. Chaotic Merge
题意:
题解:
其实这个题相当于有很多dp的起点(拓扑图的起点),那么可以边递推边加入新的起点.
而底下不断加入
d
p
x
i
,
d
p
y
j
dpx_i, dpy_j
dpxi,dpyj,就是不断加入新的起点的过程. 比如
d
p
(
i
,
j
,
0
)
+
=
d
p
y
j
dp(i, j, 0) += dpy_j
dp(i,j,0)+=dpyj 就是表示加入了一个一串 y 中的连续字符串加上一个
x
i
x_i
xi 中的字符串作为一个起点.
#include<bits/stdc++.h>usingnamespace std;constint N =1010;typedeflonglong ll;const ll mod =998244353;char x[N], y[N];
ll f[N][N][2], fx[N], fy[N];intmain(){scanf("%s%s", x +1, y +1);int n =strlen(x +1);int m =strlen(y +1);for(int i =1; i <= n; i++){if(x[i]!= x[i -1]) fx[i]= fx[i -1]+1;else fx[i]=1;}for(int i =1; i <= m; i++){if(y[i]!= y[i -1]) fy[i]= fy[i -1]+1;else fy[i]=1;}for(int i =1; i <= n; i++){for(int j =1; j <= m; j++){if(x[i]!= y[j]){
f[i][j][0]=(f[i][j][0]+ fy[j])% mod;
f[i][j][1]=(f[i][j][1]+ fx[i])% mod;}if(x[i]!= x[i -1]){
f[i][j][0]=(f[i][j][0]+ f[i -1][j][0])% mod;}if(x[i]!= y[j]){
f[i][j][0]=(f[i][j][0]+ f[i -1][j][1])% mod;
f[i][j][1]=(f[i][j][1]+ f[i][j -1][0])% mod;}if(y[j]!= y[j -1]){
f[i][j][1]=(f[i][j][1]+ f[i][j -1][1])% mod;}}}
ll ans =0;for(int i =1; i <= n; i++){for(int j =1; j <= m; j++){for(int k =0; k <=1; k++){
ans =(ans + f[i][j][k])% mod;}}}printf("%lld\n", ans);return0;}
f
(
u
,
l
e
n
)
f(u, len)
f(u,len) 表示以 u 为根节点的子树,那么我们可以两两子树遍历,
f
(
u
,
l
e
n
)
+
=
f
(
v
1
,
i
)
+
f
(
v
2
,
j
)
,
i
+
1
+
j
+
1
≤
k
.
f(u, len) += f(v_1, i) + f(v_2, j),\quad \ i+1 + j + 1 \le k.
f(u,len)+=f(v1,i)+f(v2,j),i+1+j+1≤k.
但其实我们可以依次枚举子结点,看它对答案的贡献,这也是一个常见技巧了,具体可以看代码. 而且我们知道,
f
(
v
,
j
)
f(v, j)
f(v,j) 中的
j
j
j 不可能超过子树的深度.
#include<bits/stdc++.h>usingnamespace std;constint N =5010, M =2* N;typedeflonglong ll;const ll mod =998244353;int n, k;
ll f[N][N];int h[N], e[M], ne[M], idx;voidadd(int a,int b){
e[idx]= b, ne[idx]= h[a], h[a]= idx++;}intdfs(int u,int fa){int height =0;
f[u][0]=1;for(int i = h[u]; i !=-1; i = ne[i]){int v = e[i];if(v == fa)continue;int nh =dfs(v, u);
vector<int>tmp(max(height, nh +1)+1);for(int i =0; i <= height; i++){for(int j =0; j <= nh; j++){if(i + j +1<= k){
tmp[max(i, j +1)]=(tmp[max(i, j +1)]+ f[u][i]* f[v][j]% mod)% mod;}if(i <= k){
tmp[i]=(tmp[i]+ f[u][i]* f[v][j])% mod;}}}
height =max(height, nh +1);for(int i =0; i <= height; i++){
f[u][i]= tmp[i];}}return height;}intmain(){memset(h,-1,sizeof h);scanf("%d%d",&n,&k);for(int i =1; i < n; i++){int a, b;scanf("%d%d",&a,&b);add(a, b),add(b, a);}dfs(1,-1);
ll ans =0;for(int i =0; i <= k; i++){
ans =(ans + f[1][i])% mod;}printf("%lld\n", ans);return0;}
G. Genius
题意:有n个题,从前往后的复杂度
c
i
c_i
ci 为
2
i
2^i
2i,我们如果刚解决问题
j
j
j,想要解决问题
i
i
i,就要满足
I
Q
<
∣
c
i
−
c
j
∣
IQ<|c_i-c_j|
IQ<∣ci−cj∣,解完
i
i
i 后,
I
Q
IQ
IQ 就会变为
∣
c
i
−
c
j
∣
|c_i-c_j|
∣ci−cj∣,并得到
∣
s
i
−
s
j
∣
|s_i-s_j|
∣si−sj∣ 分,刚开始
I
Q
IQ
IQ 为
0
0
0,我们可以先解任意一个题,一个题可以解决多次,我们要得出最多可以有多少分.
我们可以证明,任意两点间的
∣
c
i
−
c
j
∣
|c_i - c_j|
∣ci−cj∣ 的数值是不同的. 假设
i
>
j
i > j
i>j,因为
∣
2
i
−
2
j
∣
|2^i - 2^j|
∣2i−2j∣ 是从
j
j
j 到
i
−
1
i - 1
i−1 位全是1,其他全是0.
我们还发现,如果我们随意走出一条路径,那么我们只需要调整一下走的顺序,就是一条合法路径。因此我们只需要枚举
(
i
,
j
)
(i, j)
(i,j) 即可.
最后,我们定义
f
i
f_i
fi 表示在第 i 个点结束做题时的答案,我们第一维枚举结束的顶点,第二维倒着枚举,从
i
−
1
i - 1
i−1 到 1,这样子一定可以保证最后走的那条路是最大值.
#include<bits/stdc++.h>usingnamespace std;constint N =5010;typedeflonglong ll;
ll f[N], tag[N], s[N];intmain(){int T;scanf("%d",&T);while(T--){int n;scanf("%d",&n);memset(f,0,sizeof f);for(int i =1; i <= n; i++){scanf("%lld",&tag[i]);}for(int i =1; i <= n; i++){scanf("%lld",&s[i]);}for(int i =1; i <= n; i++){for(int j = i -1; j >=1; j--){if(tag[i]== tag[j])continue;
ll p =abs(s[i]- s[j]), fi = f[i], fj = f[j];
f[i]=max(f[i], fj + p), f[j]=max(f[j], fi + p);}}printf("%lld\n",*max_element(f +1, f + n +1));}return0;}
#include<bits/stdc++.h>usingnamespace std;constint N =10000010;int prime[N], cnt, st[N];voidsieve(int n){for(int i =2; i <= n; i++){if(!st[i]) prime[cnt++]= st[i]= i;for(int j =0; prime[j]<= n / i; j++){
st[i * prime[j]]= prime[j];if(i % prime[j]==0)break;}}}intchange(int x){int res =1;while(x >1){int u = st[x];int cnt =0;while(x % u ==0){
cnt++;
x /= u;}if(cnt &1) res *= u;}return res;}intmain(){sieve(N -1);int T;scanf("%d",&T);while(T--){
set<int> S;int n, k;scanf("%d%d",&n,&k);int ans =1, tmp;for(int i =1; i <= n; i++){scanf("%d",&tmp);
tmp =change(tmp);if(S.count(tmp)){
ans++;
S.clear();}
S.insert(tmp);}printf("%d\n", ans);}return0;}
I. Square-free division (hard version)
给一个序列
n
≤
2
e
5
n \le 2e5
n≤2e5,问最少分割成几个连续字段,使得每个连续字段中不存在两个数的积是完全平方数. 可以改变中间的某个数字至多
k
(
k
≤
20
)
k(k \le 20)
k(k≤20) 次,改为任意的数字即可.
我们先处理出
L
e
f
t
(
i
,
j
)
Left(i,j)
Left(i,j) 表示从
i
i
i 开始,可以删掉
k
k
k 个
j
j
j 个数字,满足
[
l
,
i
]
[l,i]
[l,i] 中间是合法连续子序列的最往左的左区间端点是多少. 这个可以用双指针在
O
(
n
k
)
O(nk)
O(nk) 的时间内解决.
然后令
f
(
i
,
j
)
f(i,j)
f(i,j) 表示前
i
i
i 个数字,改变
j
j
j 个数字的最少划分数是多少. 这个我们可以枚举
x
∈
[
0
,
j
]
x \in [0,j]
x∈[0,j],
l
=
L
e
f
t
(
i
,
j
)
l = Left(i,j)
l=Left(i,j),可以分为从两块,及
[
1
,
l
−
1
]
[1, l - 1]
[1,l−1] 的最优划分加上
[
l
,
i
]
[l,i]
[l,i] 作为一个完整连续子段,并且前者改变
j
−
x
j - x
j−x 次,后者改变
x
x
x 次.
f
(
i
,
j
)
=
min
{
f
(
i
,
j
)
,
f
(
l
−
1
,
j
)
+
1
}
f(i,j) = \min\{f(i,j),f(l-1,j) + 1\}
f(i,j)=min{f(i,j),f(l−1,j)+1}
初始化的时候,
f
f
f初始化为正无穷,
f
(
0
,
0
)
f(0,0)
f(0,0) 初始化为1即可.
#include<bits/stdc++.h>usingnamespace std;constint N =200010, M =10000010;int prime[M], st[M], cnt;voidsieve(int n){for(int i =2; i <= n; i++){if(!st[i]) prime[cnt++]= st[i]= i;for(int j =0; prime[j]<= n / i; j++){
st[i * prime[j]]= prime[j];if(i % prime[j]==0)break;}}}intchange(int x){
vector<int> res;while(x >1){int u = st[x];int cnt =0;while(x % u ==0){
x /= u;
cnt++;}if(cnt &1) res.push_back(u);}int ans =1;for(auto p : res){
ans = ans * p;}return ans;}int a[N], n, k, Left[N][30], f[N][30];
unordered_map<int,int>Map(N);intmain(){sieve(M -1);int T;scanf("%d",&T);while(T--){scanf("%d%d",&n,&k);for(int i =1; i <= n; i++){int x;scanf("%d",&x);
a[i]=change(x);}for(int i =1; i <= n; i++){for(int j =0; j <= k; j++){
Left[i][j]=0;
f[i][j]=1e9;}}
f[0][0]=0;for(int lim =0; lim <= k; lim++){
Map.clear();int cnt =0;for(int i =1, j =1; i <= n; i++){
Map[a[i]]++;if(Map[a[i]]>1) cnt++;while(i >= j && cnt > lim){if(Map[a[j]]>1){
cnt--;}
Map[a[j]]--;
j++;}
Left[i][lim]= j;}}for(int i =1; i <= n; i++){for(int j =0; j <= k; j++){for(int x =0; x <= j; x++){int l = Left[i][x];
f[i][j]=min(f[i][j], f[l -1][j - x]+1);}}}int ans =1e9;for(int j =0; j <= k; j++){
ans =min(ans, f[n][j]);}printf("%d\n", ans);}return0;}
给了一排电脑
n
≤
400
n \le 400
n≤400,最开始全是关闭状态,可以一个一个打开它. 当打开了
i
−
1
i - 1
i−1 和
i
+
1
i + 1
i+1 的电脑的时候,第
i
i
i 个电脑也会自动打开.
令
f
(
l
e
n
,
c
n
t
)
f(len, cnt)
f(len,cnt) 表示点亮了前
l
e
n
−
1
len - 1
len−1 个电脑,手动打开了
c
n
t
cnt
cnt 个,假如后面又打开了
k
k
k 个连续段,那么状态转移方程是
f
(
l
e
n
+
k
+
1
,
c
n
t
+
k
)
+
=
f
(
l
e
n
,
c
n
t
)
∗
C
l
e
n
+
c
n
t
c
n
t
∗
2
k
−
1
.
f(len + k + 1, cnt + k) += f(len, cnt) * C_{len + cnt}^{cnt} * 2 ^{k - 1}.
f(len+k+1,cnt+k)+=f(len,cnt)∗Clen+cntcnt∗2k−1.
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;constint N =510;
ll n, mod;
ll f[N][N], pw[N], fact[N], infact[N];
ll mod_pow(ll x, ll n){
ll res =1;while(n){if(n &1) res = res * x % mod;
x = x * x % mod;
n >>=1;}return res;}
ll C(ll a, ll b){return fact[a]* infact[a - b]% mod * infact[b]% mod;}intmain(){scanf("%lld%lld",&n,&mod);
fact[0]= infact[0]=1;for(ll i =1; i < N; i++){
fact[i]= i * fact[i -1]% mod;
infact[i]= infact[i -1]*mod_pow(i, mod -2)% mod;}
pw[0]=1;for(int i =1; i < N; i++){
pw[i]= pw[i -1]*2% mod;}
f[0][0]=1;for(int i =0; i <= n +1; i++){for(int j =0; j <= i; j++){for(int k =1; k <= n +1; k++){if(i + k +1> n +1)break;
f[i + k +1][j + k]=(f[i + k +1][j + k]+ f[i][j]*C(j + k, k)% mod * pw[k -1]% mod)% mod;}}}
ll ans =0;for(int i =1; i <= n; i++){
ans =(ans + f[n +1][i])% mod;}printf("%lld\n", ans);return0;}