The 2021 ICPC Asia Shanghai & Shenyang Regional Contest
赛前练习。
WA/RE/TLE原因总结:
2021上海
G:inv[0]未赋值为1
J(me):for循环里特判nw=0后直接continue,但实际上还需要有一步修改
B(me):vector 数组下标从 0 开始;容斥取模要注意负数;因为用 vector 存多项式系数所以注意调用的时候不要下标超限;NTT数组最好开成4倍
2021沈阳
J:cin字符数组出错(自写可速A)
L(me):NTT数组开小了(正解直接DP,要分析好复杂度)
B:最后一次WA是因为数组开小了
M(me):一开始没处理q->nq改道的情况,后来没处理nw=q的话改道时要改nw的情况
2021南京
M:特判n=1
H(me):DP时漏了一种max情况(mx1+mx2,当二者不能同时选时,只考虑了nxtmx1+mx2,没考虑mx1+nxtmx2)
D:思路有不完善之处,新加的数作为最大值时的情况算错了
2020银川
E:签到题,但第一发漏了一种情况
J:数组大小写错
G(me):读错题了(虽然能解释样例,最终做法也差不多)
上海站
[B]
分析:
首先要转化问题。从边和回路的角度考虑很巧妙,也容易想到容斥计数。之后问题就是计算选 i 条“禁边”的方案数。熟悉生成函数的话挺容易列出式子的;因为是多个小多项式相乘,所以可以用 NTT 加速,同时用分治。为了做分治,用 vector 或者结构体包一个 vector 来存多项式(系数)。
写完后又 WA, T 了几次,找出几个写错的地方:vector 数组下标从 0 开始,容斥取模要注意负数,因为用 vector 存多项式系数所以注意调用的时候不要下标超限。然后还是一直 WA 第 15 个点,对拍也拍不出错,一晚上没调出来心力交瘁。但是后来做了沈阳站的 L ,写 NTT 出了同样的错误,终于发现是 NTT 用到的一些数组下标超限了(空间开小了)。改了以后终于过了T_T。分治 NTT 因为每次都重新计算 lim,所以要格外小心下标这些。
但是还挺奇怪,按理说开 \(2n=2*10^5\) 大小就足够了,结果 \(数组大小 \leq 2*10^5+30000\) 会 WA, \(2*10^5+30000 \leq 数组大小 \leq 2*10^5+60000\) 会 RE, \(数组大小 \geq 2*10^5+70000\) 才能过……
代码如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Nd vector<int>
int const N=3e5+5,mod=998244353; //!! (2e5会WA)
int n,p[N],siz[N],cnt,rev[N],f[N],g[N];
bool vis[N];
ll fac[N],inv[N];
ll pw(ll x,int y)
{
ll ret=1;
while(y)
{
if(y&1) ret = ret*x%mod;
x = x*x%mod;
y>>=1;
}
return ret;
}
void Init()
{
fac[0]=1; inv[0]=1;
for(int i=1;i<=n;i++) fac[i] = fac[i-1]*i%mod;
inv[n] = pw(fac[n],mod-2);
for(int i=n-1;i;i--) inv[i] = inv[i+1]*(i+1)%mod;
}
ll C(int n,int m){return (n>=m)? (fac[n]*inv[m]%mod)*inv[n-m]%mod : 0;}
void dfs(int u)
{
if(vis[u])return;
vis[u]=1; siz[cnt]++;
dfs(p[u]);
}
int upt(int x){while(x>=mod)x-=mod; while(x<0)x+=mod; return x;}
void ntt(int *a,int tp,int lim)
{
for(int i=0;i<lim;i++)
if(i<rev[i])swap(a[i],a[rev[i]]);
for(int mid=1;mid<lim;mid<<=1)
{
int wn=pw(3,tp==1?(mod-1)/(mid<<1):(mod-1)-(mod-1)/(mid<<1));
for(int j=0,len=(mid<<1);j<lim;j+=len)
for(int k=0,w=1;k<mid;k++,w=(ll)w*wn%mod)
{
int x=a[j+k],y=(ll)w*a[j+mid+k]%mod;
a[j+k]=upt(x+y); a[j+mid+k]=upt(x-y);
}
}
if(tp==1)return; int inv=pw(lim,mod-2);
for(int i=0;i<lim;i++)a[i]=(ll)a[i]*inv%mod;
}
/*
struct Nd{
vector<int> a;
friend Nd operator * (Nd x, Nd y)
{
puts("in*");
int lim=1; int L=0; int tot=x.a.size()+y.a.size();
while(lim<=tot) lim<<=1, L++;
printf("lim=%d\n",lim);
for(int i=0;i<lim;i++) rev[i]=((rev[i>>1]>>1)|((i&1)<<(L-1)));
// x.a.resize(lim); y.a.resize(lim);
for(int i=0;i<lim;i++) f[i]=(i<x.a.size())?x.a[i]:0;
for(int i=0;i<lim;i++) g[i]=(i<y.a.size())?y.a[i]:0;
ntt(f,1,lim); ntt(g,1,lim);
for(int i=0;i<lim;i++) f[i]=(ll)f[i]*g[i]%mod;
ntt(f,-1,lim);
x.a.clear();
for(int i=0;i<=tot;i++)x.a.push_back(f[i]);
return x;
}
};
*/
vector< Nd > Pol;
Nd mul(Nd x,Nd y)
{
// puts("in*");
int lim=1; int L=0; int tot=x.size()+y.size();
while(lim<=tot) lim<<=1, L++;
// printf("lim=%d\n",lim);
for(int i=0;i<lim;i++) rev[i]=((rev[i>>1]>>1)|((i&1)<<(L-1)));
for(int i=0;i<lim;i++) f[i]=(i<x.size())?x[i]:0;
for(int i=0;i<lim;i++) g[i]=(i<y.size())?y[i]:0;
ntt(f,1,lim); ntt(g,1,lim);
for(int i=0;i<lim;i++) f[i]=(ll)f[i]*g[i]%mod;
ntt(f,-1,lim);
Nd ret;
for(int i=0;i<=tot;i++)ret.push_back(f[i]);
return ret;
}
Nd work(int l,int r)
{
// printf("l=%d r=%d\n",l,r);
if(l==r) return Pol[l-1]; // 注意Pol的下标从0开始T_T
int mid=((l+r)>>1);
// puts("!!");
return mul(work(l,mid),work(mid+1,r));
}
void prt(Nd x)
{
printf("len=%d: ",x.size());
for(int i=0;i<x.size();i++)printf("%d ",x[i]); puts("");
}
int main()
{
// freopen("data.txt","r",stdin);
scanf("%d",&n); Init();
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++)
if(!vis[i])cnt++,dfs(i);
for(int i=1;i<=cnt;i++)
{
// printf("i=%d\n",i);
Nd tmp; tmp.push_back(1);
for(int j=1;j<siz[i];j++)
tmp.push_back(C(siz[i],j));
Pol.push_back(tmp);
// prt(tmp);
}
Nd coe = work(1,cnt);
// prt(coe);
ll ans=0;
for(int i=0,f=1;i<=min((ll)n,(ll)coe.size()-1);i++,f*=(-1)) // min!!
ans = ((ans + (ll)f*coe[i]*fac[n-i]%mod)%mod+mod)%mod; // 注意负数!
printf("%lld\n",ans);
return 0;
}
[J]
分析:
把前缀和 s 排序后按这个顺序枚举,能够满足前面出现的位置都是 s 更大的,这样做挺巧妙。
积累过 bitset 的用法,却没有真正用过。这次算是实践了。
代码如下
#include<bits/stdc++.h>
using namespace std;
int const N=5e4+5;
int n;
struct Nd{
int sum,pos;
}s[N];
char A[N],B[N];
bitset<N> C,V,Tmp;
bool cmp(Nd a,Nd b) {return (a.sum==b.sum)?(a.pos<b.pos):(a.sum>b.sum);}
void prt()
{
printf("C:");
for(int i=1;i<=n;i++) cout<<C[i];
puts("");
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
cin>>A+1>>B+1;
s[0].sum=0; s[0].pos=0;
for(int i=1;i<=n;i++)
s[i].sum = s[i-1].sum + ((A[i]=='0')?(-1):1), s[i].pos=i;
sort(s,s+n+1,cmp); // 含s[0]
// for(int i=0;i<=n;i++) printf("s[%d]=(%d,%d)\n",i,s[i].sum,s[i].pos);
C.reset(); V.reset(); int lst=N+1;// C=1表示不合法
Tmp.reset(); for(int i=1;i<=n;i++) Tmp.set(i,1);
for(int i=0,nw;i<=n;i++) // on B[s[i].pos] !!
{
nw = s[i].pos;
// if(nw==0){V.set(n,1); continue;} //!!!
if(B[nw]=='1') C |= (V>>(n-nw));
// else C |= ((~V) >> (n-nw));
else C |= ((V^Tmp) >> (n-nw));
if((B[nw]=='1'&&s[i].sum<=0) || (B[nw]=='0'&&s[i].sum>0)) lst=min(lst,nw); // 在nw处nw不合法,则大于nw的都不合法
V.set(n-nw,1);
// printf("i=%d nw=%d\n",i,nw); prt();
}
for(int i=1;i<=n;i++)
{
// printf("i=%d\n",i);
if(i>=lst)putchar('0');
else cout<<(C[i]^1);
}
puts("");
}
return 0;
}
沈阳站
[J]
分析:
很快就有思路了,先差分,然后可以想到每个位置都是一次做到终态的,因为如果反复加减的话,说明后面有位置需要它这样来附带一些加减次数,但这还不如后面那个位置自己做这些次数的加减,答案只可能更优。所以直接 dfs 每个位置是加到终态还是减到终态就行了。
跟队友说了以后,他们似乎理解成另一种做法,先处理再差分;本质上没区别。但是写得有点复杂(而且是 bfs),然后不知哪里写挂了一直 RE 和 WA。最后时刻把 cin 字符数组改成字符就过了。比较奇怪。
下来自己又写了一下我原来的想法,很简单,很快就过了。看来以后有把握的题自己能写就写。
代码如下
#include<bits/stdc++.h>
using namespace std;
char A[10],B[10];
int a[10],b[10],ans;
int upt(int x){return (x%10+10)%10;}
void dfs(int t,int add,int mns,int use)
{
if(t==5) {ans=min(ans,use); return;}
if(a[t]<=add)dfs(t+1,add-a[t],mns,use); // use add
else dfs(t+1,0,mns+a[t]-add,use+a[t]-add);
if(10-a[t]<=mns)dfs(t+1,add,mns-(10-a[t]),use);
else dfs(t+1,add+(10-a[t])-mns,0,use+(10-a[t])-mns);
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%s%s",A+1,B+1); ans=0x3f3f3f3f;
for(int i=1;i<=4;i++)
a[i]=(int)(A[i]-'0'), b[i]=(int)(B[i]-'0');
for(int i=4;i>1;i--) a[i]-=a[i-1], b[i]-=b[i-1];
for(int i=1;i<=4;i++) a[i]=upt(b[i]-a[i]);
dfs(1,0,0,0);
printf("%d\n",ans);
}
return 0;
}
[L]
分析:
容斥+树形DP。但是一开始想 DP 是\(O(n^3)\)的,于是想用 NTT 优化成\(O(n^2logn)\),但是还是 T 了。
后来看题解,发现没有注意到转移时只需要枚举到 siz[u]/2 或者 siz[v]/2;而这样枚举的话直接 DP 即可,因为考虑它枚举的过程,会发现大约等价于枚举树上所有的两个点的点对(说大约是因为这里 siz 还除以 2 了),也就是复杂度总体是 \(O(n^2)\) 的。这个想法之前见过,感觉很妙,这里就用上了。
还试图这样减小 NTT 每轮的 \(lim\) ,结果一直 WA 在第 8 个点,百思不得其解。后来调来调去,发现 \(lim \geq siz[u]+siz[v]\) 会 WA,但是改成 \(lim \geq siz[u]/2\) 就是意料之中的 T 了。感觉是因为随意增大 \(lim\) 会出问题,下标超限什么的(比如最后一步这样求和已经大于 \(n\) 了)。以后写 NTT 时得注意精确地赋值 \(lim\) 。
代码如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int const N=4005,mod=998244353,xn=(1<<13);
int n,fa[N],hd[N],to[N<<1],nxt[N<<1],cnt,siz[N];
int rev[xn];
ll A[xn],B[xn];
ll dp[N][N][3],fac[N],inv[N],inv2[N];
int upt(ll x){while(x>=mod)x-=mod; while(x<0)x+=mod; return x;}
ll pw(ll x,int y)
{
ll ret=1;
for(;y;y>>=1,x=(x*x)%mod) if(y&1)ret=(ret*x)%mod;
return ret;
}
void Init()
{
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=(ll)fac[i-1]*i%mod;
inv[n]=pw(fac[n],mod-2);
for(int i=n-1;i>=0;i--) inv[i]=(ll)inv[i+1]*(i+1)%mod;
int nw=1;
for(int i=1;i<=n;i++) nw=upt(nw*2);
inv2[n]=pw(nw,mod-2);
for(int i=n-1;i>=0;i--) inv2[i]=upt(inv2[i+1]*2);
}
void ntt(ll *a,int tp,int lim)
{
for(int i=0;i<lim;i++)
if(i<rev[i])swap(a[i],a[rev[i]]);
for(int mid=1;mid<lim;mid<<=1)
{
int wn=pw(3,tp==1?(mod-1)/(mid<<1):(mod-1)-(mod-1)/(mid<<1));
for(int j=0,len=(mid<<1);j<lim;j+=len)
for(int k=0,w=1;k<mid;k++,w=(ll)w*wn%mod)
{
int x=a[j+k],y=(ll)w*a[j+mid+k]%mod;
a[j+k]=upt(x+y); a[j+mid+k]=upt(x-y);
}
}
if(tp==1)return; int inv=pw(lim,mod-2);
for(int i=0;i<lim;i++)a[i]=(ll)a[i]*inv%mod;
}
void add(int x,int y){to[++cnt]=y; nxt[cnt]=hd[x]; hd[x]=cnt;}
ll g[xn][3];
void dfs(int u)
{
// printf("u=%d\n",u);
dp[u][0][0]=1; siz[u]=1;
for(int i=hd[u],v;i;i=nxt[i])
{
if((v=to[i])==fa[u])continue;
fa[v]=u; dfs(v); siz[u]+=siz[v];
int lim=1; int L=0;
while(lim<=siz[u]/2) lim<<=1,L++; // siz[u]/2 (若写成siz[u]+siz[v]则WA on test8)
for(int i=0;i<lim;i++) rev[i]=((rev[i>>1]>>1)|((i&1)<<(L-1)));
// TLE
// memset(g,0,sizeof g);
// for(int j=0;j<=siz[u]/2;j++)
// for(int k=0;k<=j&&k<=siz[v]/2;k++)
// g[j][0]=(g[j][0]+dp[u][j-k][1]*(dp[v][k][0]+dp[v][k][1]))%mod;
// for(int j=0;j<=siz[u]/2;j++) dp[u][j][1]=g[j][0];
// for(int j=0;j<=siz[u]/2;j++)
// for(int k=0;k<=j&&k<=siz[v]/2;k++)
// g[j][1]=(g[j][1]+dp[u][j-k][0]*dp[v][k][0])%mod;
// for(int j=1;j<=siz[u]/2;j++) dp[u][j][1]=upt(dp[u][j][1]+g[j-1][1]);
// for(int j=0;j<=siz[u]/2;j++)
// for(int k=0;k<=j&&k<=siz[v]/2;k++)
// g[j][2]=(g[j][2]+dp[u][j-k][0]*(dp[v][k][0]+dp[v][k][1]))%mod;
// for(int j=0;j<=siz[u]/2;j++) dp[u][j][0]=g[j][2];
//约等价于上面写法,TLE,无法避免的多余时间在于dp[v]可转移的仅有siz[v]/2个,而这里扩充成siz[u]/2个
for(int j=0;j<lim;j++) A[j]=dp[u][j][1], B[j]=upt(dp[v][j][0]+dp[v][j][1]);
ntt(A,1,lim); ntt(B,1,lim);
for(int j=0;j<lim;j++) A[j]=(ll)A[j]*B[j]%mod;
ntt(A,-1,lim);
for(int k=0;k<lim;k++) dp[u][k][1]=A[k];
for(int j=0;j<lim;j++) A[j]=dp[u][j][0], B[j]=dp[v][j][0];
ntt(A,1,lim); ntt(B,1,lim);
for(int j=0;j<lim;j++) A[j]=(ll)A[j]*B[j]%mod;
ntt(A,-1,lim);
for(int k=1;k<=lim;k++) dp[u][k][1]=upt(dp[u][k][1]+A[k-1]);
for(int j=0;j<lim;j++) A[j]=dp[u][j][0], B[j]=upt(dp[v][j][0]+dp[v][j][1]);
ntt(A,1,lim); ntt(B,1,lim);
for(int j=0;j<lim;j++) A[j]=(ll)A[j]*B[j]%mod;
ntt(A,-1,lim);
for(int k=0;k<lim;k++) dp[u][k][0]=A[k];
}
}
void dfs2(int u)
{
dp[u][0][0]=1; siz[u]=1;
for(int i=hd[u],v;i;i=nxt[i])
{
if((v=to[i])==fa[u])continue;
fa[v]=u; dfs2(v);
/* TLE
siz[u]+=siz[v];
memset(g,0,sizeof(g));
for(int j=0;j<=siz[u]/2;j++)
{
g[j][0]=dp[u][j][0], g[j][1]=dp[u][j][1];
dp[u][j][0]=0; dp[u][j][1]=0;
}
for(int k=0;k<=siz[u]/2;k++)
{
for(int j=0;j<=siz[v]/2&&j<=k;j++)
{
// dp[u][k][0]=upt(dp[u][k][0] + g[k-j][0]*(dp[v][j][0]+dp[v][j][1])%mod);
// dp[u][k][1]=upt(dp[u][k][1] + g[k-j][1]*(dp[v][j][0]+dp[v][j][1])%mod);
// if(k-1-j>=0)dp[u][k][1]=upt(dp[u][k][1] + g[k-1-j][0]*dp[v][j][0]%mod);
dp[u][k][0] += g[k-j][0]*(dp[v][j][0]+dp[v][j][1]);
dp[u][k][1] += g[k-j][1]*(dp[v][j][0]+dp[v][j][1]);
if(k-1-j>=0) dp[u][k][1] += g[k-1-j][0]*dp[v][j][0];
dp[u][k][0]%=mod; dp[u][k][1]%=mod;
}
}
*/
//(AC) 此做法的复杂度为n^2,相当于枚举点对
memset(g,0,sizeof g);
for(int j=0;j<=siz[u]/2;j++){
for(int k=0;k<=siz[v]/2;k++){
g[j+k][0] += dp[u][j][0]*(dp[v][k][0]+dp[v][k][1]);
g[j+k][1] += dp[u][j][1]*(dp[v][k][0]+dp[v][k][1]);
g[j+k+1][1] += dp[u][j][0]*dp[v][k][0];
g[j+k][0] %= mod,g[j+k][1] %= mod,g[j+k+1][1] %= mod;
}
}
siz[u] = siz[u] + siz[v];
for(int j=0;j<=siz[u]/2+1;j++)
dp[u][j][0] = g[j][0],dp[u][j][1] = g[j][1];
}
}
int cal(int m){return ((ll)fac[m]*inv[m/2]%mod)*(ll)inv2[m/2]%mod;}
ll C(int n,int m){return (ll)fac[n]*fac[m]%mod*fac[n-m]%mod;}
int main()
{
// freopen("data2.txt","r",stdin);
scanf("%d",&n); n<<=1; //n个点
Init();
for(int i=1,u,v;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
dfs2(1);
// for(int k=0;k<n;k++)printf("dp[1][%d][0]=%lld, dp[1][%d][1]=%lld\n",k,dp[1][k][0],k,dp[1][k][1]);
ll ans=0;
for(int i=0,f=1;i<=n;i++,f*=(-1))
ans = ((ans + (ll)f*(dp[1][i][0]+dp[1][i][1])%mod*(ll)cal(n-2*i)%mod)%mod+mod)%mod;
printf("%lld\n",ans);
return 0;
}
/*
2
1 2
1 3
3 4
3
1 2
2 3
3 4
4 5
5 6
*/
[M]
分析:
按题解标算试着写了一下,但是不成功。
试了样例1,问题在于从 \(potat\) 到 \(potato\) 这一步,原来的最大串 \(tat\) 在后缀自动机上是一条路径 \(1-4(t)-5(a)-6(t)\) 。但是结束后新增了一个 \(7\) 号点,原来从 \(1\) 号点走 \(t\) 直接到的是 \(4\) 号点,现在变成 \(7\) 号点了。然后再加入字符 \(o\) ,新增 \(o\) 边的是 \(6\) 号点和 \(7\) 号点,没有办法回退到根再走出一个 \(to\) 来。
不知道怎么解决,先放在这里……
代码如下
#include<bits/stdc++.h>
using namespace std;
int const N=1e6+5;
int n,lst=1,cnt=1,go[N][30],fa[N],l[N],a[N],dep[N],son[N],nw,pre[N];
char s[N];
void add(int w)
{
printf("W=%d\n",w);
for(int i=1;i<=cnt;i++)printf("dep[%d]=%d pre[%d]=%d ",i,dep[i],i,pre[i]); puts("");
int p=lst,np=++cnt; lst=np; l[np]=l[p]+1;
int fre=nw;
for(;p&&!go[p][w];p=fa[p])
{
go[p][w]=np;
if(w==14)printf("%d -> %d\n",p,np);
printf("dep[%d]=%d dep[fre=%d]=%d\n",p,dep[p],fre,dep[fre]);
if(dep[p] && son[p]<(1<<w) && dep[p]<dep[fre]) fre=p; // 只有在路径上的点dep!=0
son[p] |= (1<<w);
}
printf("fre=%d nw=%d\n",fre,nw);
if(fre>-1)
while(nw!=fre) dep[nw]=0, nw=pre[nw]; // 回退
printf("after: fre=%d nw=%d\n",fre,nw);
if(!p) fa[np]=1;
else
{
int q=go[p][w];
if(l[q]==l[p]+1) fa[np]=q;
else
{
int nq=++cnt; l[nq]=l[p]+1;
fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
memcpy(go[nq],go[q],sizeof go[q]); son[nq]=son[q];
for(;go[p][w]==q;p=fa[p]) go[p][w]=nq;
}
}
printf("GO: %d -> %d\n",nw,go[nw][w]);
dep[go[nw][w]]=dep[nw]+1; pre[go[nw][w]]=nw; nw=go[nw][w]; // 新走一步
}
int main()
{
scanf("%s",s+1); n=strlen(s+1);
nw=1; dep[1]=1;
for(int i=1;i<=n;i++)
{
add(s[i]-'a');
printf("nw=%d\n",nw);
printf("%d %d\n",i-dep[nw]+2,i);
}
return 0;
}
更新:解决上面那个情况,只要路径改道就可以了!
改完以后样例能过了,但是第4个点TLE了;又加了个 \(nxt\) 数组小小优化一下,加了快写,还是T,真是没办法了……
代码如下
#include<bits/stdc++.h>
using namespace std;
int const N=1e6+5;
int n,lst=1,cnt=1,go[N][30],fa[N],l[N],a[N],dep[N],son[N],nw,pre[N],nxt[N];
char s[N];
void add(int w)
{
// printf("W=%d\n",w);
// for(int i=1;i<=cnt;i++)printf("dep[%d]=%d pre[%d]=%d ",i,dep[i],i,pre[i]); puts("");
int p=lst,np=++cnt; lst=np; l[np]=l[p]+1;
int fre=nw;
for(;p&&!go[p][w];p=fa[p])
{
go[p][w]=np;
// if(w==14)printf("%d -> %d\n",p,np);
// printf("dep[%d]=%d dep[fre=%d]=%d\n",p,dep[p],fre,dep[fre]);
if(dep[p] && son[p]<(1<<w) && dep[p]<dep[fre]) fre=p; // 只有在路径上的点dep!=0
son[p] |= (1<<w);
}
// printf("fre=%d nw=%d\n",fre,nw);
if(fre>-1)
while(nw!=fre) dep[nw]=0, nw=pre[nw], pre[nxt[nw]]=0, nxt[nw]=0; // 回退
// printf("after: fre=%d nw=%d\n",fre,nw);
if(!p) fa[np]=1;
else
{
int q=go[p][w];
if(l[q]==l[p]+1) fa[np]=q;
else
{
int nq=++cnt; l[nq]=l[p]+1;
fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
memcpy(go[nq],go[q],sizeof go[q]); son[nq]=son[q];
if(!dep[q]) {for(;go[p][w]==q;p=fa[p]) go[p][w]=nq;} // 如果q不在路径上则不影响
else // 如果q在路径上,但pre[q]->q没被改成pre[q]->nq,则也不影响
{
for(;go[p][w]==q;p=fa[p])
{
if(p==pre[q]) // 路径此点需要修改,p->q改成p->nq, q->nxt改成nq->nxt
{
dep[nq]=dep[q]; pre[nq]=p; dep[q]=0;
// for(int x=0;x<26;x++)
// if(go[q][x] && dep[go[q][x]]) {pre[go[q][x]]=nq; break;} // 上述nxt=go[q][x]
pre[nxt[q]]=nq; nxt[q]=0; pre[q]=0;
}
go[p][w]=nq;
}
}
}
}
// printf("GO: %d -> %d\n",nw,go[nw][w]);
dep[go[nw][w]]=dep[nw]+1; pre[go[nw][w]]=nw; nxt[nw]=go[nw][w]; nw=go[nw][w]; // 新走一步
}
void write(int x)
{
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
int main()
{
scanf("%s",s+1); n=strlen(s+1);
nw=1; dep[1]=1;
for(int i=1;i<=n;i++)
{
add(s[i]-'a');
// printf("nw=%d\n",nw);
// printf("%d %d\n",i-dep[nw]+2,i);
write(i-dep[nw]+2); putchar(' '); write(i); putchar('\n');
}
return 0;
}
更新2:怀疑是死循环了,在唯一的 \(while\) 循环那里加了个保险退出条件,然后第4个点变成WA了,修理了一下链表部分还是WA,唉……暂时不知怎么改。
代码如下
#include<bits/stdc++.h>
using namespace std;
int const N=1e6+5;
int n,lst=1,cnt=1,go[N][30],fa[N],l[N],a[N],dep[N],son[N],nw,pre[N],nxt[N];
char s[N];
void add(int w)
{
// printf("W=%d\n",w);
// for(int i=1;i<=cnt;i++)printf("dep[%d]=%d pre[%d]=%d ",i,dep[i],i,pre[i]); puts("");
int p=lst,np=++cnt; lst=np; l[np]=l[p]+1;
int fre=-1;
for(;p&&!go[p][w];p=fa[p])
{
go[p][w]=np;
// if(w==14)printf("%d -> %d\n",p,np);
// printf("dep[%d]=%d dep[fre=%d]=%d\n",p,dep[p],fre,dep[fre]);
if(dep[p] && son[p]<(1<<w) && (fre==-1||dep[p]<dep[fre])) fre=p; // 只有在路径上的点dep!=0
son[p] |= (1<<w);
}
// printf("fre=%d nw=%d\n",fre,nw);
if(fre>-1)
while(dep[nw]&&nw!=fre) dep[nw]=0, nw=pre[nw], pre[nxt[nw]]=0, nxt[nw]=0; // 回退
// printf("after: fre=%d nw=%d\n",fre,nw);
if(!p) fa[np]=1;
else
{
int q=go[p][w];
if(l[q]==l[p]+1) fa[np]=q;
else
{
int nq=++cnt; l[nq]=l[p]+1;
fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
memcpy(go[nq],go[q],sizeof go[q]); son[nq]=son[q];
if(!dep[q]) {for(;go[p][w]==q;p=fa[p]) go[p][w]=nq;} // 如果q不在路径上则不影响
else // 如果q在路径上,但pre[q]->q没被改成pre[q]->nq,则也不影响
{
for(;go[p][w]==q;p=fa[p])
{
if(p==pre[q]) // 路径此点需要修改,p->q改成p->nq, q->nxt改成nq->nxt
{
dep[nq]=dep[q]; pre[nq]=p; nxt[p]=nq;
// for(int x=0;x<26;x++)
// if(go[q][x] && dep[go[q][x]]) {pre[go[q][x]]=nq; break;} // 上述nxt=go[q][x]
pre[nxt[q]]=nq; nxt[nq]=nxt[q];
nxt[q]=0; pre[q]=0; dep[q]=0;
}
go[p][w]=nq;
}
}
}
}
// printf("GO: %d -> %d\n",nw,go[nw][w]);
dep[go[nw][w]]=dep[nw]+1; pre[go[nw][w]]=nw; nxt[nw]=go[nw][w]; nw=go[nw][w]; // 新走一步
}
void write(int x)
{
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
int main()
{
scanf("%s",s+1); n=strlen(s+1);
nw=1; dep[1]=1;
for(int i=1;i<=n;i++)
{
add(s[i]-'a');
// printf("nw=%d\n",nw);
// printf("%d %d\n",i-dep[nw]+2,i);
write(i-dep[nw]+2); putchar(' '); write(i); putchar('\n');
}
return 0;
}
更新3:对拍发现了一组错误数据: \(bggotiho\) ,调了调发现是因为涉及到 \(nq\) 换 \(q\) 的路径改道时,还要考虑当前路径终点 \(nw=q\) 的情况,此时要令 \(nw=nq\) 。改了以后还RE了一发,因为数组开小了;两倍字符串大小就过了~
代码如下
#include<bits/stdc++.h>
using namespace std;
int const N=2e6+5;
int n,lst=1,cnt=1,go[N][30],fa[N],l[N],a[N],dep[N],son[N],nw,pre[N],nxt[N];
char s[N];
void add(int w)
{
// printf("W=%d\n",w);
// for(int i=1;i<=cnt;i++)printf("dep[%d]=%d pre[%d]=%d ",i,dep[i],i,pre[i]); puts("");
int p=lst,np=++cnt; lst=np; l[np]=l[p]+1;
int fre=-1;
for(;p&&!go[p][w];p=fa[p])
{
go[p][w]=np;
// if(w==14)printf("%d -> %d\n",p,np);
// printf("dep[%d]=%d dep[fre=%d]=%d\n",p,dep[p],fre,dep[fre]);
if(dep[p] && son[p]<(1<<w) && (fre==-1||dep[p]<dep[fre])) fre=p; // 只有在路径上的点dep!=0
son[p] |= (1<<w);
}
// printf("fre=%d nw=%d\n",fre,nw);
if(fre>-1){while(nw!=fre) dep[nw]=0, nxt[nw]=0, nw=pre[nw], pre[nxt[nw]]=0, nxt[nw]=0;} // 回退
// printf("after: fre=%d nw=%d\n",fre,nw);
// printf("AFTER: "); for(int i=1;i<=cnt;i++)printf("dep[%d]=%d pre[%d]=%d ",i,dep[i],i,pre[i]); puts("");
if(!p) fa[np]=1;
else
{
int q=go[p][w];
if(l[q]==l[p]+1) fa[np]=q;
else
{
int nq=++cnt; l[nq]=l[p]+1;
fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
memcpy(go[nq],go[q],sizeof go[q]); son[nq]=son[q];
if(!dep[q]) {for(;go[p][w]==q;p=fa[p]) go[p][w]=nq;} // 如果q不在路径上则不影响
else // 如果q在路径上,但pre[q]->q没被改成pre[q]->nq,则也不影响
{
for(;go[p][w]==q;p=fa[p])
{
if(p==pre[q]) // 路径此点需要修改,p->q改成p->nq, q->nxt改成nq->nxt
{
if(nw==q)nw=nq; //!!!
// printf("CHANGE: "); printf("%d -> %d -> %d nq=%d\n",p,q,nxt[q],nq);
dep[nq]=dep[q]; pre[nq]=p; nxt[p]=nq;
// for(int x=0;x<26;x++)
// if(go[q][x] && dep[go[q][x]]) {pre[go[q][x]]=nq; break;} // 上述nxt=go[q][x]
pre[nxt[q]]=nq; nxt[nq]=nxt[q];
nxt[q]=0; pre[q]=0; dep[q]=0;
}
go[p][w]=nq;
}
}
}
}
// printf("GO: %d -> %d dep[%d]=%d\n",nw,go[nw][w],nw,dep[nw]);
// if(fre>-1){while(dep[nw]&&nw!=fre) dep[nw]=0, nxt[nw]=0, nw=pre[nw], pre[nxt[nw]]=0, nxt[nw]=0;} // 回退
dep[go[nw][w]]=dep[nw]+1; pre[go[nw][w]]=nw; nxt[nw]=go[nw][w]; nw=go[nw][w]; // 新走一步
}
void write(int x)
{
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
int main()
{
scanf("%s",s+1); n=strlen(s+1);
nw=1; dep[1]=1;
for(int i=1;i<=n;i++)
{
add(s[i]-'a');
// printf("cnt=%d nw=%d dep[%d]=%d\n",cnt,nw,nw,dep[nw]);
// printf("%d %d\n",i-dep[nw]+2,i);
write(i-dep[nw]+2); putchar(' '); write(i); putchar('\n');
}
return 0;
}
/*
bggotiho
1 1
2 2
2 3
4 4
5 5
5 6
5 7
5 8
*/