R
E
P
L
A
C
E
l
,
r
REPLACE\ l,\ r
REPLACEl,r,对于每一个
i
∈
[
l
,
r
]
i\in[l,r]
i∈[l,r],让
a
i
=
D
(
a
i
)
a_i=D(a_i)
ai=D(ai),其中
D
(
x
)
D(x)
D(x) 表示
x
x
x 的因子个数
S
U
M
l
r
SUM\ l\ r
SUMlr,求出
∑
i
=
l
r
a
i
\sum_{i=l}^r a_i
∑i=lrai
1
≤
n
,
m
≤
3
⋅
1
0
5
1\le n,m\le 3\cdot 10^5
1≤n,m≤3⋅105
1
≤
a
i
≤
1
0
6
1\le a_i\le 10^6
1≤ai≤106
1
≤
n
<
1
0
18
1\le n< 10^{18}
1≤n<1018
1
≤
m
≤
100
1\le m\le 100
1≤m≤100
思路
求全排列,不能爆搜,那我们可以用状压,来记录我们目前已经选了哪些数字。 然后,考虑最后整除
m
m
m,我们多记录一个状态,表示目前选完组成的数字取模
m
m
m 是多少。 就是记
d
p
[
S
]
[
j
]
dp[S][j]
dp[S][j] 表示已选数字集合为
S
S
S,且这个数字的排列的值取模
m
m
m 为
j
j
j 的方案数。
然后我们每一次新选择一个新的数字,往下简单递推即可。最终答案就是
d
p
[
a
l
l
]
[
0
]
dp[all][0]
dp[all][0]
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}typedeflonglong ll;constint MAX =1e5+50;constint MOD =1e9+7;int cnt;int arr[20];int num[20];
ll dp[1<<18][105];
ll getFac(int x){
ll res =1;for(ll i =2;i <= x;++i)res *= i;return res;}intmain(){
ll n;int m;cin >> n >> m;while(n){
arr[cnt++]= n %10;
num[n %10]++;
n /=10;}int ed =1<<cnt;
dp[0][0]=1;for(int i =0;i < ed;++i){for(int j =0;j < m;++j){if(!dp[i][j])continue;for(int k =0;k < cnt;++k){if(i>>k&1)continue;if(i ==0&& arr[k]==0)continue;
dp[i|(1<<k)][(j*10+arr[k])%m]+= dp[i][j];}}}// show(dp[ed-1][0]);for(int i =0;i <10;++i)dp[ed-1][0]/=getFac(num[i]);
cout << dp[ed-1][0];return0;}
C:Cut | CF 1516D
题意
Cut | CF 1516D 给你长度为
n
n
n 的序列
a
n
a_n
an,给你
m
m
m 个询问 每次询问,给你一个
l
,
r
l,r
l,r,问你这一段
a
l
∼
a
r
a_l\sim a_r
al∼ar 最少分成几个连续的子段,满足每个子段的
l
c
m
lcm
lcm 等于这个子段中每个元素的乘积?
1
≤
n
,
q
,
a
i
≤
1
0
5
1\le n,q,a_i\le 10^5
1≤n,q,ai≤105
思路
好
题
\color{blue}{好题}
好题 如果只是一次询问,怎么做? 首先题意的这个子段必须满足区间内元素两两不互素。 每一次从最左边
x
x
x 开始,贪心取,取到最大的
y
y
y,满足
(
a
x
,
a
x
+
1
,
⋯
,
a
y
)
(a_x,a_x+1,\cdots,a_y)
(ax,ax+1,⋯,ay) 内的元素都两两不互素。然后开始下一个区间。
这里有许多分支的思考。我们一个一个提出,一个一个解决。 (1)这个贪心有什么启发? 考虑到我们每一个元素
a
i
a_i
ai 作为子段的左顶点的话,这个子段的最右顶点的下标我们记作
n
x
t
[
i
]
nxt[i]
nxt[i] 由于左顶点不断向右移动,不互素条件越来越容易,所以我们的右顶点的下标是单调不降的。 也就是我们可以使用类似双指针的方式去得到所有的
n
x
t
[
i
]
nxt[i]
nxt[i] (2) 如何判断子段内的元素两两不互素来得到我们的
n
x
t
[
i
]
nxt[i]
nxt[i] ? 我们如果获得了每个元素
a
i
a_i
ai 的所有质因子集合
A
\mathbb{A}
A,我们对于每一个质因子开一个
v
i
s
[
p
]
vis[p]
vis[p] 表示这个子段内是否已经有这个质因子了。如果我新加进来的数字的某个
p
p
p 满足加进来之前已经
v
i
s
[
p
]
=
1
vis[p]=1
vis[p]=1 了,那当然不满足互素条件了。新加进来数字和删除数字对
v
i
s
[
p
]
vis[p]
vis[p] 的影响都很好写。 注意,一个数字的因子个数是
O
(
n
1
/
3
)
O(n^{1/3})
O(n1/3) 级别的。但是我们不要直接
O
(
n
3
/
2
)
O(n^{3/2})
O(n3/2) 去获得所有的因子,我们可以先枚举因子再枚举数,这样可以复杂度变成
O
(
n
log
n
)
O(n\log n)
O(nlogn) (3)有了
n
x
t
[
i
]
nxt[i]
nxt[i] 数组了,我们怎么求一次询问的答案? 每一次我都跳
n
x
t
[
i
]
nxt[i]
nxt[i],直到
n
x
t
[
i
]
≥
r
nxt[i]\ge r
nxt[i]≥r了,那么跳的次数当然就是这个区间内最少数量的子段了。 (4)但是多次询问,不是很优啊?怎么优化呢? 考虑我们每一次跳一次
n
x
t
[
i
]
nxt[i]
nxt[i],确实挺劣的。我们可以使用倍增表,一次跳多个,这样跳的次数就可以控制在
log
\log
log 次了。注意,我们需要改一下
n
x
t
[
i
]
nxt[i]
nxt[i] 数组的定义,改成最右的右顶点的下一个位置。这样,就可以直接使用倍增表了。 倍增表
n
x
t
[
i
]
[
j
]
nxt[i][j]
nxt[i][j] 表示从
i
i
i 开始,跳
2
j
2^j
2j 步,右区间的下一个位置在哪里。如果它
≤
r
\le r
≤r,就表示我们要跳。当然这里的
j
j
j 是从大往小枚举的。
代码
时间复杂度:
O
(
(
n
+
q
)
log
n
)
O((n+q)\log n)
O((n+q)logn)
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}typedeflonglong ll;constint MAX =1e5+50;constint MOD =1e9+7;int cnt;
bool vis[MAX];int pri[MAX];
vector<int>fac[MAX];int aa[MAX];int nxt[MAX][30];int vis2[MAX];voidshai(int x){for(int i =2;i <= x;++i){if(!vis[i])pri[++cnt]=i;for(int j =1;j <= cnt && i * pri[j]<= x;++j){
vis[i * pri[j]]=1;if(i % pri[j]==0)break;}}for(int i =1;i <= cnt;++i)for(int j =1;j * pri[i]<= x;++j){
fac[pri[i]* j].push_back(i);}}voidchange(int x,int k){int val = aa[x];for(auto it : fac[val])vis2[it]+= k;}
bool check(int nxtPos){int val = aa[nxtPos];for(auto it : fac[val])if(vis2[it])return false;return true;}intmain(){shai(100000);int n,q;scanf("%d%d",&n,&q);for(int i =1;i <= n;++i){scanf("%d",&aa[i]);}int nxtPos=0;for(int i =1;i <= n;++i){while(nxtPos+1<= n &&check(nxtPos+1)){change(nxtPos+1,1);
nxtPos++;}change(i,-1);
nxt[i][0]= nxtPos+1;// show(i,nxtPos);}for(int j =0;j <=20;++j)
nxt[n+1][j]= n+1;for(int j =1;j <=20;++j)for(int i =1;i <= n;++i)
nxt[i][j]= nxt[nxt[i][j-1]][j-1];while(q--){int l,r;scanf("%d%d",&l,&r);int cnt =0;int now = l;for(int i =20;~i;--i){if(nxt[now][i]<= r){
cnt +=(1<<i);
now = nxt[now][i];}}printf("%d\n",cnt+1);}return0;}
D:Recovering BST | CF 1025D
题意
Recovering BST | CF 1025D 给定一棵树的中序遍历顺序
a
n
a_n
an 求问你能否构建出一颗这样的
B
S
T
BST
BST 树,且满足相连的两个节点的
gcd
>
1
\gcd >1
gcd>1
2
≤
n
≤
700
2\le n\le 700
2≤n≤700
2
≤
a
i
≤
1
0
9
2\le a_i\le 10^9
2≤ai≤109
思路
上来我们肯定会去想能否定义
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 表示区间
[
i
,
j
]
[i,j]
[i,j] 搭建一棵
B
S
T
BST
BST ,其中根为
k
k
k。 然后考虑转移。我们枚举
i
,
j
,
k
i,j,k
i,j,k,只要左边找一个根,右边找一个根,满足
d
p
[
i
]
[
k
−
1
]
[
r
1
]
=
d
p
[
k
+
1
]
[
r
]
[
r
2
]
=
1
dp[i][k-1][r1]=dp[k+1][r][r2]=1
dp[i][k−1][r1]=dp[k+1][r][r2]=1,然后要求两个根和
a
k
a_k
ak 的
gcd
>
1
\gcd >1
gcd>1,就可以转移过来了。 但是这样子做的时间复杂度为
O
(
n
4
)
O(n^4)
O(n4),超时了。
然后我去瞎搞贪心构造,但是很明显是不大行的,还是得去做
d
p
dp
dp。我们得考虑优化。 上面的问题就在于转移太复杂了,状态也很复杂,虽然看起来都是必须的。 注意到一些必要的性质。如果我们能搭建起一棵
[
l
,
r
]
[l,r]
[l,r] 的树,不管这棵树的根是多少,它的父亲要么是
a
l
−
1
a_{l-1}
al−1,要么是
a
r
+
1
a_{r+1}
ar+1,要么它没有父亲了。
所以,我们定义状态
d
p
[
i
]
[
j
]
[
0
/
1
]
dp[i][j][0/1]
dp[i][j][0/1] 表示是否能搭建起一棵
[
i
,
j
]
[i,j]
[i,j] 的树,且根的父亲为
a
i
a_{i}
ai (或者根的父亲为
a
r
a_{r}
ar)。注意这里的范围! 考虑转移。
d
p
[
i
′
]
[
j
′
]
[
0
/
1
]
=
d
p
[
i
]
[
k
]
[
1
]
&
d
p
[
k
]
[
j
]
[
0
]
dp[i^\prime][j^\prime][0/1]=dp[i][k][1]\&dp[k][j][0]
dp[i′][j′][0/1]=dp[i][k][1]&dp[k][j][0],且
gcd
(
k
,
f
a
)
>
1
\gcd(k,fa)>1
gcd(k,fa)>1。 很明显,我们把根为
k
k
k,拉出来,左子树可以满足向右的父亲为
k
k
k,右子树可以满足向左的父亲为
k
k
k。 然后再看是
0
/
1
0/1
0/1 决定
f
a
fa
fa 是
i
−
1
i-1
i−1 还是
j
+
1
j+1
j+1,然后转移。 最后如果存在
d
p
[
1
]
[
n
]
[
0
/
1
]
=
1
dp[1][n][0/1]=1
dp[1][n][0/1]=1 那就是可以重构的,否则不行
注意我们可以提前预处理
g
g
[
i
]
[
j
]
=
gcd
(
a
i
,
a
j
)
gg[i][j]=\gcd(a_i,a_j)
gg[i][j]=gcd(ai,aj),否则又要多一个
log
\log
log
代码
时间复杂度:
O
(
n
3
)
O(n^3)
O(n3),但是有很多冗余,所以不会跑满。
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}typedeflonglong ll;constint MAX =1e5+50;constint MOD =1e9+7;int aa[705];int gg[705][705];
bool dp[705][705][2];intmain(){int n;cin >> n;for(int i =1;i <= n;++i)cin >> aa[i],dp[i][i][0]= dp[i][i][1]=1;for(int i =0;i <= n +1;++i)for(int j =0;j <= n +1;++j)
gg[i][j]=__gcd(aa[i],aa[j]);for(int Len =1;Len <= n;++Len)for(int L =1;L + Len -1<= n;++L){int R = L + Len -1;// show(L,R,dp[L][R][0],dp[L][R][1]);for(int k = L;k <= R;++k){if(dp[L][k][1]&& dp[k][R][0]){if(gg[L-1][k]>1)dp[L-1][R][0]=1;if(gg[R+1][k]>1)dp[L][R+1][1]=1;if(L ==1&& R == n){puts("Yes");return0;}}}}puts("No");return0;}/**
4
23 247 299 703
*/
E:Array and Operations | CF 498C
题意
Array and Operations | CF 498C 给定一个序列
a
n
a_n
an 给定
m
m
m 对边
(
i
m
,
j
m
)
(i_m,j_m)
(im,jm),其中满足
i
k
+
j
k
i_k+j_k
ik+jk 是奇数,且
i
k
<
j
k
i_k<j_k
ik<jk 你每次可以选择一对边
(
x
,
y
)
(x,y)
(x,y),然后选择一个
v
v
v,满足
v
>
1
v>1
v>1 且
v
∣
a
x
v|a_{x}
v∣ax 且
v
∣
a
y
v|a_y
v∣ay 然后你就成功一次操作,然后让
a
x
=
a
x
/
v
a_x=a_x/v
ax=ax/v,
a
y
=
a
y
/
v
a_y=a_y/v
ay=ay/v 问你最多进行多少次操作
2
≤
n
≤
100
2\le n\le 100
2≤n≤100
1
≤
m
≤
100
1\le m\le 100
1≤m≤100
1
≤
a
i
≤
1
0
9
1\le a_i\le 10^9
1≤ai≤109
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}typedeflonglong ll;constint MAX =1e6+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;structedge{int to;
ll cap;int rev;};
vector<edge>G[MAX];int level[MAX];/// 深度数组int iter[MAX];/// 当前弧优化voidadd_edge(int from,int to,ll cap){
G[from].push_back({to,cap,G[to].size()});
G[to].push_back({from,0,G[from].size()-1});}voidbfs(int s){memset(level,-1,sizeof(level));
level[s]=0;
queue<int>Q;
Q.push(s);while(!Q.empty()){int v = Q.front();Q.pop();for(auto it : G[v]){if(it.cap >0&& level[it.to]<0){
level[it.to]= level[v]+1;
Q.push(it.to);}}}}
ll dfs(int v,int t,ll f){if(v == t)return f;for(int&i = iter[v];i < G[v].size();++i){
edge &e = G[v][i];if(e.cap >0&& level[v]< level[e.to]){
ll d =dfs(e.to,t,min(f,e.cap));if(d >0){
e.cap -= d;
G[e.to][e.rev].cap += d;return d;}}}return0;}
ll max_flow(int s,int t){
ll flow =0;while(1){bfs(s);if(level[t]<0)return flow;memset(iter,0,sizeof(iter));
ll f =-1;while(f){
f =dfs(s,t,LINF);
flow += f;}}}int aa[MAX];
vector<pair<int,int>>V[MAX];intmain(){int n,m;cin >> n >> m;for(int i =1;i <= n;++i)cin >> aa[i];int cnt =0;for(int i =1;i <= n;++i){int tmp = aa[i];for(int j =2;j * j <= tmp;++j){if(tmp % j ==0){while(tmp % j ==0){
tmp /= j;
V[i].push_back(make_pair(++cnt,j));}}}if(tmp >1)V[i].push_back(make_pair(++cnt,tmp));}int S =0,T =2* cnt +1;for(int i =1;i <= n;++i){for(auto it : V[i]){int x = it.first;add_edge(x,x+cnt,1);if(i&1)add_edge(S,x,INF);elseadd_edge(x+cnt,T,INF);}}while(m--){int ta,tb;
cin >> ta >> tb;for(auto it : V[ta])for(auto it2: V[tb]){if(it.second == it2.second){if(ta&1)add_edge(it.first+cnt,it2.first,INF);elseadd_edge(it2.first+cnt,it.first,INF);}}}
cout <<max_flow(S,T);return0;}/**
*/
F:Send Boxes to Alice (Hard Version) | CF 1254B2
题意
Send Boxes to Alice (Hard Version) | CF 1254B2 给定一个序列
a
n
a_n
an 每一次移动,你可以选择两个相邻的元素(也就是
a
b
s
(
p
1
−
p
2
)
=
1
abs(p1-p2)=1
abs(p1−p2)=1),然后把其中一个位置的元素
+
1
+1
+1,另一个位置的元素
−
1
-1
−1 问你最少的移动次数,满足这个序列的
gcd
>
1
\gcd>1
gcd>1 如果做不到,输出
−
1
-1
−1 数据满足至少存在一个正整数
a
i
a_i
ai
1
≤
n
≤
1
0
6
1\le n\le 10^6
1≤n≤106
0
≤
a
i
≤
1
0
6
0\le a_i\le 10^6
0≤ai≤106
思路
好
题
\color{blue}{好题}
好题 看到这种题,想到对于每一个
gcd
=
k
\gcd=k
gcd=k,我都去
c
h
e
c
k
check
check 一下,算一下移动次数的最少值。 但是每一次操作,让一个加一另一个减一的操作非常不好搞。 然后我们换一种思路。我们记前缀和
S
i
=
a
1
+
a
2
+
⋯
+
a
i
S_i=a_1+a_2+\cdots+a_i
Si=a1+a2+⋯+ai 容易得到,我们要让
k
∣
S
i
k|S_i
k∣Si,且我们每一次的移动操作只会让一个
S
i
S_i
Si 加一或者减一。 这就非常好做了。
对于每一个特定的
k
k
k,
S
i
S_i
Si 要减或者加的操作来满足是
k
k
k 的倍数,也就是我们取
min
{
S
i
%
k
,
k
−
S
i
%
k
}
\min\{S_i\%k,k-S_i\%k\}
min{Si%k,k−Si%k}
但是对于每一个
k
k
k 我们都求一遍还是不大行呀。这个时候我们想到,我们的
k
k
k 只取合理的质数即可。因为在上述
%
k
\%k
%k 的前提下,且段是成倍数的情况下,段长度越小,灵活性越大。
代码
时间复杂度:
O
(
n
×
log
n
)
O(n \times \log n)
O(n×logn)
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}typedeflonglong ll;constint MAX =1e6+50;constint MOD =1e9+7;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;int aa[MAX];
ll pre[MAX];
ll check(ll x,int n){
ll ans =0;for(int i =1;i <= n;++i){
ans +=min(pre[i]%x,x-(pre[i]%x));}// show(x,ans);return ans;}intmain(){
ll sum =0;int n;scanf("%d",&n);for(int i =1;i <= n;++i)scanf("%d",&aa[i]),sum += aa[i],pre[i]= pre[i-1]+ aa[i];
ll ans = LINF;for(ll i =2;i * i <= sum;++i){if(sum % i ==0){
ans =min(ans,check(i,n));while(sum % i ==0)sum /= i;}}if(sum >1)ans =min(ans,check(sum,n));if(ans == LINF)puts("-1");elseprintf("%lld",ans);return0;}