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");
}