Codeforces Round #539Ȟȟȡ (Div. 1) 简要题解
Codeforces Round #539 (Div. 1)
A. Sasha and a Bit of Relax
description
给一个序列\(a_i\),求有多少长度为偶数的区间\([l,r]\)满足\([l,mid]\)的异或和等于\([mid+1,r]\)的异或和。
solution
等价于询问有多少长度为偶数的区间异或和为\(0\)。
只需要两个位置的异或前缀和与下标奇偶性相同即可组成一个合法区间。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
int n;pair<int,int>a[300005];long long ans;
int main(){
n=gi();
for(int i=1,s=0;i<=n;++i)s^=gi(),a[i]=make_pair(s,i&1);
sort(a,a+n+1);
for(int i=0,j=0;i<=n;i=j=j+1){
while(j<n&&a[j+1]==a[i])++j;
ans+=1ll*(j-i+1)*(j-i)>>1;
}
printf("%lld\n",ans);return 0;
}
B. Sasha and One More Name
description
给一个回文串,求将其拆分成最小的段数后按任意顺序拼接起来后得到另一个不同的回文串,或判断无解。
solution
无解当且仅当全都是同一个字符或者是长度为奇数,除正中间外全都为同一个字符。
否则答案至多为\(2\),只需要判断\(1\)是否可行就可以了。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=5005;
int n,fg=1;char s[N],t[N];
bool check1(){
for(int i=1;i<=n;++i)if(s[i]!=t[i])return 1;
return 0;
}
bool check2(){
for(int i=1;i<=n>>1;++i)if(t[i]!=t[n-i+1])return 0;
return 1;
}
int main(){
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n>>1;++i)if(s[i]!=s[1])fg=0;
if(fg)return puts("Impossible"),0;
for(int i=1;i<n;++i){
for(int j=1;j<=i;++j)t[n-i+j]=s[j];
for(int j=i+1;j<=n;++j)t[j-i]=s[j];
if(check1()&&check2())return puts("1"),0;
}
return puts("2"),0;
}
C. Sasha and a Patient Friend
description
有一个分段的一次函数,初始时全为\(0\)。定义一个事件\((t,s)\)为,从\(x=t\)开始,将函数的斜率改成\(s\),直至下一个事件出现为止。现在支持三种操作,一种是给出\(t,s\),加入一个事件\((t,s)\),一种是给出\(t\),删除\(x=t\)上的事件,一种是给出\(l,r,v\),问只考虑定义域\([l,r]\),一个\(f(l)=v\)且斜率为上述事件所描述的分段函数(若\(x=l\)没有事件则认为斜率为\(0\))的第一个零点在哪里,或判断无解。
solution
平衡树每个节点维护区间最小值、区间末尾的值以及一些端点处的下标、斜率之类的即可。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll long long
const int N=1e5+5;
int ls[N],rs[N],tim[N],spd[N],tL[N],tR[N],spdR[N],rd[N],tot,rt;
ll res[N],mn[N];
void up(int x){
tL[x]=tR[x]=tim[x];spdR[x]=spd[x];mn[x]=res[x]=0;
if(ls[x]){
tL[x]=tL[ls[x]];
mn[x]=min(mn[x],mn[ls[x]]);
res[x]+=res[ls[x]]+1ll*spdR[ls[x]]*(tim[x]-tR[ls[x]]);
mn[x]=min(mn[x],res[x]);
}
if(rs[x]){
tR[x]=tR[rs[x]];spdR[x]=spdR[rs[x]];
res[x]+=1ll*spd[x]*(tL[rs[x]]-tim[x]);
mn[x]=min(mn[x],res[x]+mn[rs[x]]);
res[x]+=res[rs[x]];
mn[x]=min(mn[x],res[x]);
}
}
void split(int x,int k,int &a,int &b){
if(!x){a=b=0;return;}
if(tim[x]<=k)a=x,split(rs[x],k,rs[a],b),up(a);
else b=x,split(ls[x],k,a,ls[b]),up(b);
}
int merge(int x,int y){
if(!x||!y)return x|y;
if(rd[x]<rd[y])return rs[x]=merge(rs[x],y),up(x),x;
else return ls[y]=merge(x,ls[y]),up(y),y;
}
double query(int x,int r,ll v){
if(ls[x]){
if(v+mn[ls[x]]<=0)return query(ls[x],tim[x],v);
v+=res[ls[x]]+1ll*spdR[ls[x]]*(tim[x]-tR[ls[x]]);
if(v<=0)return tim[x]-1.0*v/spdR[ls[x]];
}
if(rs[x]){
v+=1ll*spd[x]*(tL[rs[x]]-tim[x]);
if(v<=0)return tL[rs[x]]-1.0*v/spd[x];
if(v+mn[rs[x]]<=0)return query(rs[x],r,v);
v+=res[rs[x]];
}
v+=1ll*spdR[x]*(r-tR[x]);return r-1.0*v/spdR[x];
}
int main(){
int q=gi();while(q--){
int op=gi();
if(op==1){
tim[++tot]=gi();spd[tot]=gi();rd[tot]=rand()*rand();
int x,y;split(rt,tim[tot],x,y);
up(tot);rt=merge(x,merge(tot,y));
}else if(op==2){
int t=gi(),x,y,z;
split(rt,t,x,z);split(x,t-1,x,y);
rt=merge(x,z);
}else{
int l=gi(),r=gi(),v=gi(),x,y,z;
if(!v){printf("%d\n",l);continue;}
split(rt,r,x,z);split(x,l-1,x,y);
if(!y||v+min(mn[y],res[y]+1ll*spdR[y]*(r-tR[y]))>0)puts("-1");
else
printf("%.6lf\n",query(y,r,v));
rt=merge(x,merge(y,z));
}
}
return 0;
}
D. Sasha and Interesting Fact from Graph Theory
description
将\(n\)个点连成一棵树,每条边的权值在\([1,m]\)内,求使得\(a,b\)两点在树上的距离(边权和)恰好为\(m\)的连边及确定边权的方案数。
solution
枚举\(a,b\)路径上有\(i\)个点(包括\(a,b\)),方案数为“\(n-2\)个点中选\(i-2\)个排列的方案数”\(\times\)“将\(m\)的边权分配到这\(i\)条边上去的方案数”\(\times\)"剩下\(n-i\)个点连成树的方案数"\(\times\)“剩下\(n-i\)条边任意分配权值的方案数”。
若\(n\)个点已经被分成了\(m\)个连通块,每个连通块的大小是\(a_i\),那么其生成树的方案数为\(n^{m-2}\prod_{i=1}^ma_i\)。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=2e6+5;
const int mod=1e9+7;
int n,m,inv[N],jc[N],jcn[N],ans;
int fastpow(int a,int b){
int res=1;
while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
return res;
}
int C(int n,int m){return 1ll*jc[n]*jcn[m]%mod*jcn[n-m]%mod;}
int P(int n,int m){return 1ll*jc[n]*jcn[n-m]%mod;}
int main(){
n=gi();m=gi();
inv[1]=jc[0]=jcn[0]=1;
for(int i=2;i<N;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<N;++i)jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=1ll*jcn[i-1]*inv[i]%mod;
for(int i=2;i<=n&&i<=m+1;++i)ans=(ans+1ll*P(n-2,i-2)*C(m-1,i-2)%mod*(i==n?1:1ll*fastpow(n,n-i-1)*i%mod)%mod*fastpow(m,n-i)%mod)%mod;
printf("%d\n",ans);return 0;
}
E. Sasha and a Very Easy Test
description
区间乘,单点除(保证整除),区间求和。对一个不是质数的数取模。
solution
线段树维护区间内与模数互质部分的和以及每个模数包含的质因子的次幂。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=1e5+5;
const int M=2e6+5;
int n,mod,pri[9],tot,pw[9][M],q;
void exgcd(int a,int b,int &x,int &y){
if(!b){x=1,y=0;return;}
exgcd(b,a%b,y,x),y-=a/b*x;
}
int getinv(int a){
int x,y;exgcd(a,mod,x,y);return (x%mod+mod)%mod;
}
struct data{
int sum,s[9];
data(){
sum=0;
for(int i=0;i<tot;++i)s[i]=0;
}
data(int x){
if(!x){
for(int i=0;i<tot;++i)s[i]=M-1;
sum=0;
}else{
for(int i=0;i<tot;++i){
s[i]=0;
while(x%pri[i]==0)++s[i],x/=pri[i];
}
sum=x;
}
}
data inv(){
data c;c.sum=getinv(sum);
for(int i=0;i<tot;++i)c.s[i]=-s[i];
return c;
}
int val(){
int res=sum;
for(int i=0;i<tot;++i)res=1ll*res*pw[i][s[i]]%mod;
return res;
}
}t[N<<2],tag[N<<2];
data operator + (data a,data b){
data c;int s1=a.sum,s2=b.sum;
for(int i=0;i<tot;++i){
c.s[i]=min(a.s[i],b.s[i]);
s1=1ll*s1*pw[i][a.s[i]-c.s[i]]%mod;
s2=1ll*s2*pw[i][b.s[i]-c.s[i]]%mod;
}
c.sum=(s1+s2)%mod;return c;
}
data operator * (data a,data b){
data c;c.sum=1ll*a.sum*b.sum%mod;
for(int i=0;i<tot;++i)c.s[i]=a.s[i]+b.s[i];
return c;
}
void build(int x,int l,int r){
tag[x]=data(1);
if(l==r){t[x]=data(gi());return;}
int mid=l+r>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);
t[x]=t[x<<1]+t[x<<1|1];
}
void cover(int x,data v){
t[x]=t[x]*v;tag[x]=tag[x]*v;
}
void down(int x){
cover(x<<1,tag[x]);cover(x<<1|1,tag[x]);tag[x]=data(1);
}
void modify(int x,int l,int r,int ql,int qr,data v){
if(l>=ql&&r<=qr){cover(x,v);return;}
down(x);int mid=l+r>>1;
if(ql<=mid)modify(x<<1,l,mid,ql,qr,v);
if(qr>mid)modify(x<<1|1,mid+1,r,ql,qr,v);
t[x]=t[x<<1]+t[x<<1|1];
}
data query(int x,int l,int r,int ql,int qr){
if(l>=ql&&r<=qr)return t[x];
down(x);int mid=l+r>>1;data res=data(0);
if(ql<=mid)res=res+query(x<<1,l,mid,ql,qr);
if(qr>mid)res=res+query(x<<1|1,mid+1,r,ql,qr);
return res;
}
int main(){
n=gi();mod=gi();int x=mod;
for(int i=2;i*i<=x;++i)
if(x%i==0){
pri[tot++]=i;
while(x%i==0)x/=i;
}
if(x>1)pri[tot++]=x;
for(int i=0;i<tot;++i)
for(int j=pw[i][0]=1;j<M;++j)
pw[i][j]=1ll*pw[i][j-1]*pri[i]%mod;
build(1,1,n);q=gi();while(q--){
int op=gi(),x=gi(),y=gi();
if(op==1)modify(1,1,n,x,y,data(gi()));
if(op==2)modify(1,1,n,x,x,data(y).inv());
if(op==3)printf("%d\n",query(1,1,n,x,y).val());
}
return 0;
}
F. Sasha and Algorithm of Silence's Sounds
description
一个\(n\times m\)的网格图,每个格子上有一个数字,它们构成一个\(n\times m\)的排列。求有多少个区间\([l,r]\)满足权值在这个区间内的所有点在网格图上形成一棵树(一个连通块、不包含环)。
solution
一棵树包含两个条件:不成环,且连通块个数为\(1\)。
首先不成环的限制可以用\(LCT+\)单调指针解决。
现在要求连通块个数为\(1\),由于构成一棵树,那么连通块个数就等于点数减边数。由于点数已知,所以只需要维护区间边数就行了。
稍加转化可以变成线段树区间加区间求\(1\)的个数(等价于求最小值及其个数)的操作。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define pi pair<int,int>
const int N=2e5+5;
int n,m,tot,f[1005][1005],px[N],py[N],fa[N],ch[2][N],rev[N];
int L=1,R=1,dx[]={1,0,-1,0},dy[]={0,1,0,-1},tmp[10],tag[N<<2];
pi sum[N<<2];long long ans=1;
bool son(int x){return x==ch[1][fa[x]];}
bool isroot(int x){return x!=ch[0][fa[x]]&&x!=ch[1][fa[x]];}
void rotate(int x){
int y=fa[x],z=fa[y],c=son(x);
ch[c][y]=ch[c^1][x];if(ch[c][y])fa[ch[c][y]]=y;
fa[x]=z;if(!isroot(y))ch[son(y)][z]=x;
ch[c^1][x]=y;fa[y]=x;
}
void rever(int x){swap(ch[0][x],ch[1][x]);rev[x]^=1;}
void alldown(int x){
if(!isroot(x))alldown(fa[x]);
if(rev[x])rever(ch[0][x]),rever(ch[1][x]),rev[x]=0;
}
void splay(int x){
alldown(x);
for(int y=fa[x];!isroot(x);rotate(x),y=fa[x])
if(!isroot(y))son(x)^son(y)?rotate(x):rotate(y);
}
void access(int x){for(int y=0;x;y=x,x=fa[x])splay(x),ch[1][x]=y;}
void makeroot(int x){access(x);splay(x);rever(x);}
int findroot(int x){access(x);splay(x);while(ch[0][x])x=ch[0][x];splay(x);return x;}
void split(int x,int y){makeroot(x);access(y);splay(y);}
void link(int x,int y){makeroot(x);fa[x]=y;}
void cut(int x,int y){split(x,y);fa[x]=ch[0][y]=0;}
bool check(){
int len=0;
for(int d=0;d<4;++d){
int x=px[R+1]+dx[d],y=py[R+1]+dy[d];
if(!x||x==n+1||!y||y==m+1||f[x][y]<L||f[x][y]>R)continue;
tmp[++len]=findroot(f[x][y]);
}
sort(tmp+1,tmp+len+1);
for(int i=1;i<len;++i)if(tmp[i]==tmp[i+1])return false;
return true;
}
pi operator+(pi a,pi b){
pi c;c.first=min(a.first,b.first);
if(c.first==a.first)c.second+=a.second;
if(c.first==b.first)c.second+=b.second;
return c;
}
void build(int x,int l,int r){
if(l==r){sum[x]=make_pair(0,1);return;}
int mid=l+r>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);
sum[x]=sum[x<<1]+sum[x<<1|1];
}
void cover(int x,int v){sum[x].first+=v;tag[x]+=v;}
void down(int x){
if(!tag[x])return;
cover(x<<1,tag[x]);cover(x<<1|1,tag[x]);tag[x]=0;
}
void modify(int x,int l,int r,int ql,int qr,int v){
if(l>=ql&&r<=qr){cover(x,v);return;}
down(x);int mid=l+r>>1;
if(ql<=mid)modify(x<<1,l,mid,ql,qr,v);
if(qr>mid)modify(x<<1|1,mid+1,r,ql,qr,v);
sum[x]=sum[x<<1]+sum[x<<1|1];
}
pi query(int x,int l,int r,int ql,int qr){
if(l>=ql&&r<=qr)return sum[x];
down(x);int mid=l+r>>1;pi res=make_pair(1<<30,0);
if(ql<=mid)res=res+query(x<<1,l,mid,ql,qr);
if(qr>mid)res=res+query(x<<1|1,mid+1,r,ql,qr);
return res;
}
int main(){
n=gi();m=gi();tot=n*m;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
f[i][j]=gi(),px[f[i][j]]=i,py[f[i][j]]=j;
build(1,1,tot);modify(1,1,tot,1,1,1);
while(R<tot){
while(L<R&&!check()){
for(int d=0;d<4;++d){
int x=px[L]+dx[d],y=py[L]+dy[d];
if(!x||x==n+1||!y||y==m+1||f[x][y]<L||f[x][y]>R)continue;
cut(L,f[x][y]);
}
++L;
}
++R;modify(1,1,tot,L,R,1);
for(int d=0;d<4;++d){
int x=px[R]+dx[d],y=py[R]+dy[d];
if(!x||x==n+1||!y||y==m+1||f[x][y]<L||f[x][y]>R)continue;
link(R,f[x][y]);modify(1,1,tot,L,f[x][y],-1);
}
pi res=query(1,1,tot,L,R);
ans+=res.first==1?res.second:0;
}
printf("%lld\n",ans);return 0;
}
[Codeforces Round #542 Alex Lopashev Thanks-Round] (Div. 1)
A. Toy Train
description
\(n\)个站台顺次连成一个环,每个站台上都有若干待运出的糖果,每个糖果被指定了要运往\(b_i\)号站台。有一辆小火车沿着站台顺时针方向走,每到一个站台时可以卸任意数量的糖果但只能装至多一个糖果。求火车从每个站台出发将所有糖果送到指定地点的最小花费。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=5005;
int n,m,ans[N];vector<int>E[N];
int main(){
n=gi();m=gi();
for(int i=1,a,b;i<=m;++i)a=gi(),b=gi(),E[a].push_back(b);
for(int i=1;i<=n;++i)
if(E[i].size()){
int mn=1<<30;
for(int x:E[i])mn=min(mn,(x+n-i)%n);
ans[i]=(int)(E[i].size()-1)*n+mn;
}else ans[i]=-1<<30;
for(int i=1;i<=n;++i){
int res=0;
for(int j=1;j<=n;++j)res=max(res,ans[j]+(j+n-i)%n);
printf("%d ",res);
}
puts("");return 0;
}
solution
每个站台选一个最近的糖果留给最后一次走,每次求答案时扫一遍所有车站即可。
B. Wrong Answer
description
给一个数组,求\(\max_{1\le l \le r\le n}\{(r-l+1)\sum_{i=l}^ra_i\}\)。
有一份代码直接求最大子段和再乘上区间答案后输出。你需要构造数据将这份代码卡掉。
solution
因为未知量很多所以构造方法也有很多。
一种方法是,将序列构造为\(\{-1,x\}\),这样错误的代码会输出\(x\)而正确结果应该是\(2(x-1)\)。\(x\)部分的长度可以是任意的。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=2000;
int main(){
int k=gi()+N;
printf("%d\n-1 ",N);
for(int i=2;i<=N;++i)printf("%d ",min(k,1000000)),k-=min(k,1000000);
return 0;
}
C. Morse Code
description
有一个长度为\(n\)的\(01\)串,定义一个\(01\)串的划分为将这个\(01\)串拆成若干长度不超过\(4\)的小段(其中有四种长度为\(4\)的串不能选)的方案数。对于每一个前缀,求这个前缀中所有本质不同子串的划分数之和模\(10^9+7\)。
solution
要求本质不同?那就对于每种本质不同的子串,在其第一次出现的位置上计算贡献就好咯。
先\(O(n^2)dp\)一下求出任意区间划分的方案数,再拉个\(SAM\)随便搞搞即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=3005;
const int M=1e4+5;
const int mod=1e9+7;
int n,s[N],L[M],fa[M],len[M],tr[M][2],tot=1,lst=1,f[N][N],ans[N];
vector<int>E[M];
inline void add(int &x,int y){x+=y;x>=mod?x-=mod:x;}
void extend(int c){
int u=++tot,v=lst;len[u]=len[v]+1;lst=u;
while(v&&!tr[v][c])tr[v][c]=u,v=fa[v];
if(!v)fa[u]=1;
else{
int x=tr[v][c];
if(len[x]==len[v]+1)fa[u]=x;
else{
int y=++tot;
tr[y][0]=tr[x][0];tr[y][1]=tr[x][1];
fa[y]=fa[x];fa[x]=fa[u]=y;len[y]=len[v]+1;
while(v&&tr[v][c]==x)tr[v][c]=y,v=fa[v];
}
}
}
void dfs(int u){
for(int v:E[u])dfs(v),L[u]=min(L[u],L[v]);
for(int i=len[fa[u]]+1;i<=len[u];++i)add(ans[L[u]],f[L[u]-i+1][L[u]]);
}
bool check(int i){
if(s[i-3]==0&&s[i-2]==0&&s[i-1]==1&&s[i]==1)return false;
if(s[i-3]==0&&s[i-2]==1&&s[i-1]==0&&s[i]==1)return false;
if(s[i-3]==1&&s[i-2]==1&&s[i-1]==1&&s[i]==0)return false;
if(s[i-3]==1&&s[i-2]==1&&s[i-1]==1&&s[i]==1)return false;
return true;
}
int main(){
n=gi();memset(L,63,sizeof(L));
for(int i=1;i<=n;++i)s[i]=gi(),extend(s[i]),L[lst]=i;
for(int i=1;i<=n;++i){
f[i][i-1]=1;
for(int j=i;j<=n;++j){
f[i][j]=f[i][j-1];
if(j>=i+1)add(f[i][j],f[i][j-2]);
if(j>=i+2)add(f[i][j],f[i][j-3]);
if(j>=i+3&&check(j))add(f[i][j],f[i][j-4]);
}
}
for(int i=2;i<=tot;++i)E[fa[i]].push_back(i);
dfs(1);
for(int i=1;i<=n;++i)add(ans[i],ans[i-1]);
for(int i=1;i<=n;++i)printf("%d\n",ans[i]);
return 0;
}
D. Isolation
description
求将\(\{a_i\}\)分成若干段使每一段内都有恰好\(k\)个数出现了恰好一次的方案数模\(998244353\)。
solution
右端点从左往右扫,维护\(pre_i\)表示\(i\)前面最近的一个与\(a_i\)相等数的位置(没有则为\(0\)),每次将\([pre_i+1,i]\)这段区间\(+1\),将\([pre_{pre_i+1},pre_i]\)这段区间\(-1\)(如果\(pre_i\neq0\)的话),然后只要查前缀所有恰好等于\(k\)的位置的\(dp\)值之和就行了。
发现根本不会用传统数据结构维护。于是考虑分块。然后就做完了。
值得注意的一点块内的权值范围是\(O(\sqrt n)\)级别的。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=1e5+5;
const int B=350;
const int mod=998244353;
int n,k,bl[N],L[B],R[B],tag[B],mn[B],mx[B],pre[N],lst[N],f[N],num[N];
vector<int>ans[B];
inline void add(int &x,int y){x+=y;x>=mod?x-=mod:x;}
void rebuild(int x){
mn[x]=1<<30;mx[x]=-1<<30;
for(int i=L[x];i<=R[x];++i){
num[i]+=tag[x];
mn[x]=min(mn[x],num[i]);mx[x]=max(mx[x],num[i]);
}
tag[x]=0;ans[x].clear();ans[x].resize(mx[x]-mn[x]+1);
for(int i=L[x];i<=R[x];++i)add(ans[x][num[i]-mn[x]],f[i-1]);
for(int i=1;i<=mx[x]-mn[x];++i)add(ans[x][i],ans[x][i-1]);
}
int cal(int x){
int t=k-tag[x];if(t<mn[x])return 0;
return ans[x][min(t-mn[x],mx[x]-mn[x])];
}
void modify(int l,int ed,int v){
while(l<=ed){
int x=bl[l],r=min(R[x],ed);
if(l==L[x]&&r==R[x])tag[x]+=v;
else{
for(int i=l;i<=r;++i)num[i]+=v;
rebuild(x);
}
l=r+1;
}
}
int query(int l,int ed){
int res=0;
while(l<=ed){
int x=bl[l],r=min(R[x],ed);
if(l==L[x]&&r==R[x])add(res,cal(x));
else{
rebuild(x);
for(int i=l;i<=r;++i)if(num[i]<=k)add(res,f[i-1]);
}
l=r+1;
}
return res;
}
int main(){
n=gi();k=gi();f[0]=1;
for(int i=1;i<=n;++i){
bl[i]=(i-1)/B+1;
if(!L[bl[i]])L[bl[i]]=i;R[bl[i]]=i;
}
rebuild(1);
for(int i=1;i<=n;++i){
int x=gi();pre[i]=lst[x];lst[x]=i;
modify(pre[i]+1,i,1);
if(pre[i])modify(pre[pre[i]]+1,pre[i],-1);
f[i]=query(1,i);if(i<n)rebuild(bl[i+1]);
}
printf("%d\n",f[n]);return 0;
}
E. Legendary Tree
description
交互题。
有一棵树。你每次可以给交互库两个点集\(S,T\)和一个点\(x\),表示询问有多少对\((s,t),s\in S,t\in T\),满足\(x\)在\((s,t)\)的路径上。你需要还原出这棵树的形态。
\(n\le500\),询问次数不超过\(11111\)次。
solution
令\(S=\{1\},T=\{2,3,...,n\},x=i(i\in[2,n])\),即可询问出以\(1\)为根时\(i\)号点的子树大小。
将所有点按照子树大小排序,从小到大加入一个点集\(P\)。每次将点\(x\)加入点集前,\(x\)的所有直接儿子一定都在点集里,所以可以依次二分找到每个儿子(二分找到点集中的第一个儿子,将其删去,并重复此过程直至没有儿子),最后将其加入点集\(P\)。
这样的询问复杂度是\(O(n(\log n+2))\)的。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define pb push_back
int query(vector<int>T,int v){
if(!T.size())return 0;
printf("1\n1\n%d\n",(int)T.size());
for(int x:T)printf("%d ",x);printf("\n%d\n",v);
fflush(stdout);return gi();
}
int sz[505];vector<int>V,S,E[505];
bool cmp(int i,int j){return sz[i]<sz[j];}
int main(){
int n=gi();
for(int i=2;i<=n;++i)V.pb(i);
for(int i=2;i<=n;++i)sz[i]=query(V,i);
sort(V.begin(),V.end(),cmp);
for(int x:V){
int k=query(S,x);
while(k--){
int l=0,r=S.size()-2,res=r+1;
while(l<=r){
int mid=l+r>>1;
vector<int>tmp;
for(int i=0;i<=mid;++i)tmp.pb(S[i]);
if(query(tmp,x))res=mid,r=mid-1;
else l=mid+1;
}
E[x].pb(S[res]);S.erase(S.begin()+res);
}
S.pb(x);
}
for(int x:S)E[1].pb(x);
puts("ANSWER");
for(int i=1;i<=n;++i)for(int v:E[i])printf("%d %d\n",i,v);
return 0;
}
Codeforces Round #543 (Div. 1, based on Technocup 2019 Final Round)
A. Diana and Liana
description
一条长度为\(m\)的彩带,每个位置上有个颜色\(a_i\),你可以删掉彩带上的若干位置,但不能改变原有的相对顺序,剩下的部分会被从前往后每\(k\)个一起被切成一段,最后不足\(k\)就丢掉不管。你需要保证最终能够切出至少\(n\)段,且至少存在一段满足:给定可重集\(\{b_i\},|\{b_i\}|=s\),要求这个可重集是这一段内的颜色集合(可重集)的子集。
solution
考虑求出一些区间满足给定集合是这个区间的颜色集合的子集。枚举右端点,左端点显然单调不降,所以只要用两个单调指针扫一遍并构造方案就行了。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=5e5+5;
int m,k,n,s,a[N],b[N],c[N],tot;
int main(){
m=gi();k=gi();n=gi();s=gi();
for(int i=1;i<=m;++i)a[i]=gi();
for(int i=1;i<=s;++i)++b[gi()];
for(int i=1;i<N;++i)if(b[i])++tot;
for(int r=1,l=1;r<=m;++r){
++c[a[r]];tot-=(c[a[r]]==b[a[r]]);
while(l<=m&&r-l+1>k&&c[a[l]]>b[a[l]])--c[a[l]],++l;
if(!tot&&r-l+1>=k&&(l-1)/k+(m-r)/k+1>=n){
printf("%d\n",(l-1)%k+r-l+1-k);
for(int i=1;i<=(l-1)%k;++i)printf("%d ",i);
for(int i=l,j=0;i<=r;++i){
if(c[a[i]]>b[a[i]]&&j<r-l+1-k)printf("%d ",i),++j;
--c[a[i]];
}
puts("");return 0;
}
}
puts("-1");return 0;
}
B. Once in a casino
description
有一个长度为\(n\)的字符集大小为\(0-9\)的字符串,每次可以选择相邻的两个位置同时\(+1\)或\(-1\),要求\(0\)不能被\(-1\),\(9\)不能被\(+1\),需要通过一系列操作使\(A\)串变成\(B\)串。求最小操作数并输出前\(10^5\)步操作。
solution
在不管\(0\)和\(9\)的限制下求出的最小操作步数就是前一问答案。
第二问可以直接从前往后每次操作最靠前的且能够被操作的位置。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=1e5+5;
int n,s[N];char a[N],b[N];long long ans;
void work(int x,int y){
printf("%d %d\n",x,y);a[x]+=y;a[x+1]+=y;
if(!--ans)exit(0);
}
void dfs(int x,int y){
if(a[x+1]+y<'0'||a[x+1]+y>'9')dfs(x+1,-y);
work(x,y);
}
int main(){
n=gi();scanf("%s%s",a+1,b+1);
for(int i=1;i<n;++i)s[i]=b[i]-a[i]-s[i-1];
if(s[n-1]!=b[n]-a[n])return puts("-1"),0;
for(int i=1;i<n;++i)ans+=abs(s[i]);
printf("%lld\n",ans);ans=min(ans,100000ll);
for(int i=1;i<n;++i)while(a[i]!=b[i])dfs(i,a[i]<b[i]?1:-1);
}
C. Compress String
description
有一个长度为\(n\)的字符串,你需要将其划分为若干段,每段需要满足:要么长度为\(1\),此时需要付出\(a\)的代价;要么这一段是前面所有段顺序链接起来形成的串的子串,此时需要付出\(b\)的代价。求划分的最小代价。
solution
有一个很显然的\(dp\),可以每次\(O(n)\)枚举转移点再\(O(n)\)判断是否满足第二种要求即可做到\(O(n^3)\)。
发现满足第二种要求的转移点一定是一段连续后缀。进一步的,这段后缀的起始位置是单调的,所以只需要用单调指针维护这个位置,再用单调队列优化转移就行了。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ull unsigned long long
const int N=5005;
const ull base=10007;
int n,a,b,q[N],hd=1,tl,f[N];char s[N];ull hsh[N],pw[N];
ull cal(int l,int r){return hsh[r]-hsh[l-1]*pw[r-l+1];}
bool check(int l,int r,int x,int y){
if(y-x>r-l)return false;ull val=cal(x,y);
for(int i=l;i+y-x<=r;++i)if(cal(i,i+y-x)==val)return true;
return false;
}
int main(){
n=gi();a=gi();b=gi();scanf("%s",s+1);
for(int i=1;i<=n;++i)hsh[i]=hsh[i-1]*base+s[i];
for(int i=pw[0]=1;i<=n;++i)pw[i]=pw[i-1]*base;
for(int i=1,j=1;i<=n;++i){
f[i]=f[i-1]+a;
while(j<i&&!check(1,j,j+1,i))++j;
while(hd<=tl&&q[hd]<j)++hd;
if(hd<=tl)f[i]=min(f[i],f[q[hd]]+b);
while(hd<=tl&&f[i]<=f[q[tl]])--tl;
q[++tl]=i;
}
printf("%d\n",f[n]);return 0;
}
D. Power Tree
description
给定一棵树,每个点有个选择的代价,需要用最小的代价选出一些点,使得对于每个叶子,它被选取的祖先的集合非空且不互相同。求最小代价,并求每个点是否可能出现在一种最优方案中。
solution
显然如果有\(m\)个叶子就一定会恰好选\(m\)个点。而如果一棵子树内有\(x\)个叶子,这棵子树内一定会被选\(x-1\)或\(x\)个点。
所以直接记\(f_u,g_u\)表示子树里选了\(x/x-1\)个点的最小代价,转移为
\(g_u=\min_v\{\sum_{w\neq v}f_w+g_v\},f_u=\min(\sum_{v}f_v,g_u+c_u)\)
最小代价即为\(f_1\)。构造方案可以对每个\(dp\)状态记录其是否为最优,倒着还原一遍\(dp\)过程即可。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll long long
const int N=2e5+5;
const ll inf=1ll<<60;
int n,mrk_f[N],mrk_g[N],s[N],m;ll c[N],f[N],g[N];
vector<int>E[N];
void dfs1(int u,int fa){
if(fa&&E[u].size()==1){f[u]=c[u];return;}
ll tmp=inf;
for(int v:E[u])
if(v^fa){
dfs1(v,u);
f[u]+=f[v];g[u]+=f[v];tmp=min(tmp,g[v]-f[v]);
}
g[u]+=tmp;f[u]=min(f[u],g[u]+c[u]);
}
void dfs2(int u,int fa){
if(mrk_f[u]){
if(f[u]==g[u]+c[u])s[++m]=u,mrk_g[u]=1;
ll sum=0;
for(int v:E[u])if(v^fa)sum+=f[v];
if(sum==f[u])
for(int v:E[u])if(v^fa)mrk_f[v]=1;
}
if(mrk_g[u]){
ll tmp=inf;int cnt=0;
for(int v:E[u])if(v^fa)tmp=min(tmp,g[v]-f[v]);
for(int v:E[u])if(v^fa)cnt+=(tmp==g[v]-f[v]);
for(int v:E[u])if(v^fa){
if(cnt>1||tmp<g[v]-f[v])mrk_f[v]=1;
if(tmp==g[v]-f[v])mrk_g[v]=1;
}
}
for(int v:E[u])if(v^fa)dfs2(v,u);
}
int main(){
n=gi();
for(int i=1;i<=n;++i)c[i]=gi();
for(int i=1;i<n;++i){
int x=gi(),y=gi();
E[x].push_back(y);E[y].push_back(x);
}
dfs1(1,0);mrk_f[1]=1;dfs2(1,0);sort(s+1,s+m+1);
printf("%lld %d\n",f[1],m);
for(int i=1;i<=m;++i)printf("%d ",s[i]);
puts("");return 0;
}
E. The very same Munchhausen
description
记\(S(n)\)为\(n\)在十进制下各位数字之和,给出\(a\),求一个\(n\)满足\(S(an)=S(n)/a\)。
solution
从低位向高位依次确定\(n\)。状态需要记录\(an\)中当前位向上一位进位进位的值以及当前已确定部分的\(aS(an)-S(n)\)的值。\(bfs\)即可。上界开到\(2k\)左右就能过了。
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
const int N=2005;
int a,n,vis[N][N<<1],s[N*N];pair<pi,int>pre[N][N<<1];
queue<pi>Q;
void add(int x,int y,int z){
int nx=(x+z*a)/10,ny=y+(x+z*a)%10*a-z;
if(ny<=-N||ny>=N||vis[nx][ny+N])return;
Q.push(mk(nx,ny));vis[nx][ny+N]=1;pre[nx][ny+N]=mk(mk(x,y),z);
}
int main(){
a=gi();for(int i=1;i<10;++i)add(0,0,i);
while(!Q.empty()){
int x=Q.front().fi,y=Q.front().se;Q.pop();
if(x==0&&y==0)
while(233){
s[n++]=pre[x][y+N].se;
int px=pre[x][y+N].fi.fi,py=pre[x][y+N].fi.se;
if(px==0&&py==0){
int p=0;while(!s[p])++p;
while(p<n)putchar(s[p]+'0'),++p;
return 0;
}
x=px,y=py;
}
for(int z=0;z<10;++z)add(x,y,z);
}
puts("-1");return 0;
}
F. Secret Letters
description
小P和小W要互相写信。沿时间轴依次发生了\(n\)个事件,每个形如小P或小W写个一封信想要寄给对方。每封信有两种可选的寄出方式,一种是花费\(d\)的代价直接传送给对方,另一种是把信丢给小R同时从小R手中拿走对方给自己的信。时间轴上第\(n+1\)个事件是两人同时去小R那里取信,小R手中每一封信保留每一单位时间需要付出\(c\)的代价。求最小代价。
solution
假设从第\(i\)时刻起小R手中有了一封信。
那么接下来每当小P或小W要连续给对方寄若干封信时,他先去一次小R那里一定不亏。剩下的信就从留给小R和直接传送两者中取代价小的即可。
按时间轴从后往前做,枚举\(i\)计算答案即可。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll long long
const int N=1e5+5;
int n,c,d,a[N];char b[N];ll sum,ans;
int main(){
n=gi();c=gi();d=gi();ans=1ll*n*d;
for(int i=1;i<=n;++i)a[i]=gi(),b[i]=getchar();
a[n+1]=gi();
for(int i=n,lst;i;--i){
if(b[i]==b[i+1])sum+=min(d,(lst-a[i+1])*c);
else lst=a[i+1];
ans=min(ans,1ll*(a[n+1]-a[i])*c+sum+1ll*(i-1)*d);
}
printf("%lld\n",ans);return 0;
}
Codeforces Round #545 (Div. 1)
咕了,可以去看yyb的博客。