Codeforces Round #423 (Div. 1, rated, based on VK Cup Finals)
CF827A String Reconstruction
分析
考慮維護每個左端點通過一個區間覆蓋到的最大右端點,然後直接雙指標鋪一下就可以了
程式碼
#include <cstdio> #include <cctype> #include <cstring> using namespace std; const int N=1000011; char s[N]; int mx,m,len[N],r[N],L[N]; int iut(){ int ans=0; char c=getchar(); while (!isdigit(c)) c=getchar(); while (isdigit(c)) ans=ans*10+c-48,c=getchar(); return ans; } int main(){ m=iut(); for (int i=1;i<=m;++i){ scanf("%s",s+r[i-1]+1); len[i]=strlen(s+r[i-1]+1); r[i]=r[i-1]+len[i]; for (int j=iut();j;--j){ int x=iut(); if (mx<x+len[i]-1) mx=x+len[i]-1; if (len[L[x]]<=len[i]) L[x]=i; } } for (int i=1,j=0,t;i<=mx;++i){ if (L[i]&&j<i+len[L[i]]-1) j=i+len[L[i]]-1,t=L[i]; if (j<i) putchar(97); else putchar(s[r[t]+i-j]); } return 0; }
CF827B High Load
分析
類似於菊花圖直接構造出長度相近的 \(k\) 條鏈,這樣一定是最優的
程式碼
#include <iostream> using namespace std; int n,m,ans; int main(){ ios::sync_with_stdio(0); cin>>n>>m,ans=(n-1+m-1)/m*2; if ((n-1)%m==1) --ans; cout<<ans<<endl; for (int i=1;i<=m;++i) cout<<"1 "<<i+1<<endl; for (int i=m+2;i<=n;++i) cout<<i-m<<" "<<i<<endl; return 0; }
CF827C DNA Evolution
分析
因為長度只有十,所以根據模數和餘數直接開 \(\frac{10*(10-1)}{2}=45\) 棵線段樹維護區間字母個數即可
理論上的空間為 \(O((n+m)\log n)\)
程式碼
#include <cstdio> #include <cctype> #include <cstring> using namespace std; const int N=100011; char s[N]; int w[N<<6][4],ls[N<<6],rs[N<<6],a[N],n,cnt,rt[11][11]; int iut(){ int ans=0; char c=getchar(); while (!isdigit(c)) c=getchar(); while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar(); return ans; } void print(int ans){ if (ans>9) print(ans/10); putchar(ans%10+48); } int rk(char ch){ if (ch=='A') return 0; else if (ch=='T') return 1; else if (ch=='G') return 2; else return 3; } void update(int &rt,int l,int r,int x,int y,int z){ if (!rt) rt=++cnt; w[rt][y]+=z; if (l==r) return; int mid=(l+r)>>1; if (x<=mid) update(ls[rt],l,mid,x,y,z); else update(rs[rt],mid+1,r,x,y,z); } int query(int rt,int l,int r,int x,int y,int z){ if (!rt) return 0; if (l==x&&r==y) return w[rt][z]; int mid=(l+r)>>1; if (y<=mid) return query(ls[rt],l,mid,x,y,z); else if (x>mid) return query(rs[rt],mid+1,r,x,y,z); else return query(ls[rt],l,mid,x,mid,z)+query(rs[rt],mid+1,r,mid+1,y,z); } int main(){ scanf("%s",s+1),n=strlen(s+1); for (int i=1;i<=n;++i){ a[i]=rk(s[i]); for (int j=1;j<=10;++j) update(rt[j][i%j],1,n,i,a[i],1); } for (int T=iut();T;--T){ int opt=iut(); if (opt==1){ int x=iut(),y=rk(getchar()); for (int j=1;j<=10;++j){ update(rt[j][x%j],1,n,x,a[x],-1); update(rt[j][x%j],1,n,x,y,1); } a[x]=y; }else{ int l=iut(),r=iut(),len,ans=0; scanf("%s",s+1),len=strlen(s+1); if (len>r-l+1) len=r-l+1; for (int j=1;j<=len;++j) ans+=query(rt[len][(l+j-1)%len],1,n,l,r,rk(s[j])); print(ans),putchar(10); } } return 0; }
CF827D Best Edge Weight
分析
先建出一棵最小生成樹,觀察到非樹邊的最大邊權就是樹上路徑最大邊權減一,這個可以用倍增(ST表)來維護。
然後樹邊的最大邊權就是所有經過它的非樹邊的最小邊權減一,如果沒有非樹邊就是-1,這個可以用逆ST表來維護。
程式碼
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=200011; struct node{int y,rk,next;}e[N<<1]; struct rec{int x,y,w;}a[N];
int f[N][18],g[N][18],dp[N][18],F[N],dep[N],et=1,ans[N],rk[N],n,m,as[N],cho[N]; bool tree[N];
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
void print(int ans){
if (ans<0) ans=-ans,putchar('-');
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
void Min(int &x,int y){x=x<y?x:y;}
int getf(int u){return F[u]==u?u:F[u]=getf(F[u]);}
bool cmp(int x,int y){return a[x].w<a[y].w;}
int Get(int x,int y){return a[x].w>a[y].w?x:y;}
void dfs(int x,int fa){
for (int I=0;I<17&&f[x][I];++I)
f[x][I+1]=f[f[x][I]][I],
g[x][I+1]=Get(g[x][I],g[f[x][I]][I]);
for (int i=as[x];i;i=e[i].next)
if (e[i].y!=fa){
f[e[i].y][0]=x,g[e[i].y][0]=e[i].rk;
dep[e[i].y]=dep[x]+1,dfs(e[i].y,x);
}
}
int lca(int x,int y){
if (dep[x]<dep[y]) x^=y,y^=x,x^=y;
for (int z=dep[x]-dep[y];z;z&=z-1) x=f[x][cho[-z&z]];
if (x==y) return x;
for (int i=17;~i;--i)
if (f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int main(){
n=iut(),m=iut();
for (int i=0;i<18;++i) cho[1<<i]=i;
for (int i=1;i<=n;++i) F[i]=i;
for (int i=1;i<=m;++i) a[i]=(rec){iut(),iut(),iut()},rk[i]=i;
sort(rk+1,rk+1+m,cmp);
for (int i=1;i<=m;++i){
rec t=a[rk[i]]; int fa=getf(t.x),fb=getf(t.y);
if (fa!=fb){
F[fa]=fb,tree[rk[i]]=1;
e[++et]=(node){t.y,rk[i],as[t.x]},as[t.x]=et;
e[++et]=(node){t.x,rk[i],as[t.y]},as[t.y]=et;
}
}
dfs(1,0);
memset(dp,0x3f,sizeof(dp));
for (int i=1;i<=m;++i)
if (!tree[i]){
int X=a[i].x,Y=a[i].y,Lca=lca(X,Y);
for (int z=dep[X]-dep[Lca];z;z&=z-1){
Min(dp[X][cho[-z&z]],a[i].w);
ans[i]=Get(ans[i],g[X][cho[-z&z]]),X=f[X][cho[-z&z]];
}
for (int z=dep[Y]-dep[Lca];z;z&=z-1){
Min(dp[Y][cho[-z&z]],a[i].w);
ans[i]=Get(ans[i],g[Y][cho[-z&z]]),Y=f[Y][cho[-z&z]];
}
ans[i]=a[ans[i]].w;
}
for (int j=17;j;--j)
for (int i=1;i<=n;++i)
if (dp[i][j]!=dp[0][0])
Min(dp[f[i][j-1]][j-1],dp[i][j]),
Min(dp[i][j-1],dp[i][j]);
for (int i=2;i<=n;++i)
if (dp[i][0]!=dp[0][0]) ans[g[i][0]]=dp[i][0];
for (int i=1;i<=m;++i) print(ans[i]-1),putchar(i==n?10:32);
return 0;
}
CF827E Rusty String
分析
往右移動 \(d\) 步重合那麼它一定有一個長度為 \(n-1-d\) 的Border(萬用字元)
不如先用萬用字元的方法,那就是 \(S[i]\) 和 \(S[n-x+i]\) 匹配,
把後面這個字串反轉成 \(S'[n-1-(n-x+i)]\) 也就是 \(S'[x-1+i]\)
多項式乘法後的項被放在了 \(x-1\),匹配函式就是 \((S[i]-S[j])^2S[i]S[j]\)
直接三次多項式乘法就可以了,這裡寫的是NTT
然後發現樣例都過不了,是因為上面的是充分不必要條件,因為未知字元並不是萬用字元,
所以如果 \(kd\) 的 Border不對,那麼 \(d\) 的Border也不對,再 \(O(n\log n)\) 判斷一下就可以了
程式碼
#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstring>
#include <algorithm>
#define mem(f,n) memset(f,0,sizeof(int)*(n))
#define cpy(f,g,n) memcpy(f,g,sizeof(int)*(n))
using namespace std;
const int mod=998244353,i3=(mod+1)/3,i2=(mod+1)/2,N=500011;
typedef long long lll; typedef unsigned long long ull;
int n,Gmi[31],Imi[31],a[N],b[N],Ans[N],period[N],len,ff[N<<2],gg[N<<2],tt[N<<2];
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
int ksm(int x,int y){
int ans=1;
for (;y;y>>=1,x=1ll*x*x%mod)
if (y&1) ans=1ll*ans*x%mod;
return ans;
}
namespace Theoretic{
int rev[N<<2],LAST; ull Wt[N<<2],F[N<<2];
void Pro(int n){
if (LAST==n) return; LAST=n,Wt[0]=1;
for (int i=0;i<n;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)?n>>1:0);
}
void NTT(int *f,int n,int op){
Pro(n);
for (int i=0;i<n;++i) F[i]=f[rev[i]];
for (int o=1,len=1;len<n;++o,len<<=1){
int W=(op==1)?Gmi[o]:Imi[o];
for (int j=1;j<len;++j) Wt[j]=Wt[j-1]*W%mod;
for (int i=0;i<n;i+=len+len)
for (int j=0;j<len;++j){
int t=Wt[j]*F[i|j|len]%mod;
F[i|j|len]=F[i|j]+mod-t,F[i|j]+=t;
}
if (o==10) for (int j=0;j<n;++j) F[j]%=mod;
}
if (op==-1){
int invn=ksm(n,mod-2);
for (int i=0;i<n;++i) F[i]=F[i]%mod*invn%mod;
}else for (int i=0;i<n;++i) F[i]%=mod;
for (int i=0;i<n;++i) f[i]=F[i];
}
void Cb(int *f,int *g,int n){
for (int i=0;i<n;++i) f[i]=1ll*f[i]*g[i]%mod;
}
}
void GmiImi(){
for (int i=0;i<31;++i) Gmi[i]=ksm(3,(mod-1)/(1<<i));
for (int i=0;i<31;++i) Imi[i]=ksm(i3,(mod-1)/(1<<i));
}
int main(){
GmiImi();
for (int T=iut();T;--T){
n=iut(),Ans[0]=0;
for (int i=0;i<n;++i){
char ch=getchar();
while (!isalpha(ch)&&ch!='?') ch=getchar();
b[n-i-1]=a[i]=(ch=='?')?0:(ch=='V'?1:2);
}
for (len=1;len<n*2;len<<=1);
for (int i=0;i<n;++i) ff[i]=a[i]*a[i]*a[i];
for (int i=0;i<n;++i) gg[i]=b[i];
Theoretic::NTT(ff,len,1),Theoretic::NTT(gg,len,1),Theoretic::Cb(ff,gg,len);
for (int i=0;i<len;++i) tt[i]=(tt[i]+ff[i])%mod;
mem(ff+n,len-n),mem(gg+n,len-n);
for (int i=0;i<n;++i) ff[i]=a[i];
for (int i=0;i<n;++i) gg[i]=b[i]*b[i]*b[i];
Theoretic::NTT(ff,len,1),Theoretic::NTT(gg,len,1),Theoretic::Cb(ff,gg,len);
for (int i=0;i<len;++i) tt[i]=(tt[i]+ff[i])%mod;
mem(ff+n,len-n),mem(gg+n,len-n);
for (int i=0;i<n;++i) ff[i]=a[i]*a[i];
for (int i=0;i<n;++i) gg[i]=b[i]*b[i];
Theoretic::NTT(ff,len,1),Theoretic::NTT(gg,len,1),Theoretic::Cb(ff,gg,len);
for (int i=0;i<len;++i) tt[i]=(tt[i]-2ll*ff[i]+mod*2)%mod;
mem(ff,len),mem(gg,len),Theoretic::NTT(tt,len,-1);
for (int i=1;i<=n;++i)
if (!tt[n-1-i]) period[i]=1;
for (int i=1;i<=n;++i){
for (int j=i+i;j<=n&&period[i];j+=i)
if (!period[j]) period[i]=0;
if (period[i]) Ans[++Ans[0]]=i,period[i]=0;
}
print(Ans[0]),putchar(10),mem(tt,len);
for (int i=1;i<=Ans[0];++i)
print(Ans[i]),putchar(i==Ans[0]?10:32);
}
return 0;
}
CF827F Dirty Arkady's Kitchen
分析
無向邊的話,人可以一直在這條邊上反覆橫跳,那麼一個點理應要被拆成奇點和偶點。
設 \(dis[x][0/1]\) 表示從起點走到 \(x\) 的步數為偶數/奇數步時的最晚時間
然後將無向邊拆成奇數特供和偶數特供,把它們丟進一個按照最早通過時間排序
如果當前的最晚時間不能滿足的話,就先把它丟進一個延遲放入的數組裡,等到有其它邊啟動時再將這條邊啟動。
這樣小根堆裡最多隻會有 \(O(m)\) 條邊,所以時間複雜度為 \(O(m\log m)\)
程式碼
#include <cstdio>
#include <cctype>
#include <queue>
#include <cstring>
using namespace std;
const int N=500011;
struct rec{
int x,y,l,r;
bool operator <(const rec &t)const{
return l>t.l;
}
};
vector<rec>e[N][2];
priority_queue<rec>q;
int n,m,dis[N][2];
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
int max(int a,int b){return a>b?a:b;}
int main(){
n=iut(),m=iut();
if (n==1) return !printf("0");
for (int i=1;i<=m;++i){
int x=iut(),y=iut(),l=iut(),r=iut()-1,opt=(r-l)&1;
if (l<r){
q.push((rec){x,y,l+1,r-(!opt)});
q.push((rec){y,x,l+1,r-(!opt)});
}
q.push((rec){x,y,l,r-opt}),q.push((rec){y,x,l,r-opt});
}
memset(dis,0xcf,sizeof(dis)),dis[1][0]=0;
while (!q.empty()){
rec t=q.top(); int opt=t.l&1; q.pop();
if (dis[t.x][opt]>=t.l){
if (t.y==n) return !printf("%d",t.l+1);
if (dis[t.y][opt^1]<=t.r){
dis[t.y][opt^1]=t.r+1;
int len=e[t.y][opt^1].size();
for (int i=0;i<len;++i){
rec _t=e[t.y][opt^1][i];
if (t.l+1<=_t.r) _t.l=t.l+1,q.push(_t);
}
e[t.y][opt^1].clear();
}
}else e[t.x][opt].push_back(t);
}
return !printf("-1");
}