Codeforces 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的博客。
Codeforces Div. 1 泛做