A. gift
分析
如果 (A,B) 中的元素是已知的,那么我们只需要对于每一个 (i) 由 (A_i) 向 (B_i)连边,假设一共形成了 (cnt) 个环,那么最终的答案就是 (n-cnt)。
现在元素是不确定的,仍然按照上面的方式连边,那么除了环以外最终会形成四种链((0,x)(x,0)(0,0)(x,y))。
设这些链分别有有 (c_{01},c_{10},c_{00},c_{11}) 个。
对于最后一种链因为 (x) 和 (y) 只出现了一次,所以可以把 (x) 和 (y) 看成同一个数,忽略这一条链,答案是不影响的。
对于第一种链,设 (f[i]) 为用这些 ((0,x)) 链恰好形成 (i) 个环的方案数。
直接求不好求,考虑使用二项式反演,设 (g[i]) 为用这些 ((0,x)) 链至少形成 (i) 个环的方案数。
那么 (g[k]=sum_{i=k}^{c_{01}} inom{c_{01}}{i}s(i,j)(c_{00}+c_{01}-i)^{underline{c_{01}-i}})
含义就是选出 (i) 条链构成 (k) 个环,这 (k) 个环只由 ((0,x)) 链构成。
剩下的 ((0,x)) 随意组合,可以和 ((0,x)) 相接,也可以和 ((0,0)) 相接,不用考虑是不是形成环。
如果和 ((0,x)) 相接,最终得到的还是 ((0,x)),如果和 ((0,0)) 相接,得到的是 ((0,0)),但是 ((0,0)) 的数量加一减一并没有受到影响。
无论怎么接 ((0,0)) 的数量都不变。
反演一下就能得到 (f) 数组的答案。
对于第二种链同理。
对于第三种链,设 (r[i]) 为用这些 ((0,0)) 链恰好形成 (i) 个环的方案数,
那么 (r[i]=s(c_{00},i) c_{00}!)。
乘上阶乘的含义是边可以任意排列。
最终的答案就是对这三个数组跑一遍背包,直接跑是三次方的,可以先跑前两个再跑最后一个。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=2e3+5,mod=998244353;
inline int addmod(rg int now1,rg int now2){
return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
return now1*=now2,now1>=mod?now1%mod:now1;
}
int n,a[maxn],b[maxn],C[maxn][maxn],s[maxn][maxn],A[maxn][maxn],jc[maxn],in[maxn],out[maxn],c00,c10,c01,cir;
bool vis[maxn];
void pre(){
for(rg int i=1;i<=n;i++) in[i]=out[i]=-1;
for(rg int i=1;i<=n;i++) out[a[i]]=b[i],in[b[i]]=a[i];
for(rg int i=1;i<=n;i++){
if(out[i]!=-1 && in[i]==-1){
rg int now=i;
while(now && out[now]!=-1) now=out[now];
if(!now) c10++;
}
}
for(rg int i=1;i<=n;i++){
if(in[i]!=-1 && out[i]==-1){
rg int now=i;
while(now && in[now]!=-1) now=in[now];
if(!now) c01++;
}
}
for(rg int i=1;i<=n;i++) if(a[i]==0 && b[i]==0) c00++;
for(rg int i=1;i<=n;i++){
if(in[i]==0){
rg int now=i;
while(now && out[now]!=-1) now=out[now];
if(!now) c00++;
}
}
for(rg int i=1;i<=n;i++){
if(out[i]>0 && !vis[i]){
rg int now=out[i];
vis[now]=1;
while(now!=i && now && out[now]) now=out[now],vis[now]=1;
if(now==i) cir++;
}
}
}
int f1[maxn],f2[maxn],g1[maxn],g2[maxn],f3[maxn],ans[maxn],tmp[maxn];
int main(){
n=read();
for(rg int i=1;i<=n;i++) a[i]=read();
for(rg int i=1;i<=n;i++) b[i]=read();
pre();
s[0][0]=1;
for(rg int i=1;i<maxn;i++){
for(rg int j=1;j<maxn;j++){
s[i][j]=addmod(s[i-1][j-1],mulmod(i-1,s[i-1][j]));
}
}
C[0][0]=1;
for(rg int i=1;i<maxn;i++) C[i][0]=1;
for(rg int i=1;i<maxn;i++){
for(rg int j=1;j<maxn;j++){
C[i][j]=addmod(C[i-1][j],C[i-1][j-1]);
}
}
jc[0]=1;
for(rg int i=1;i<maxn;i++) jc[i]=mulmod(jc[i-1],i);
for(rg int i=0;i<maxn;i++){
for(rg int j=0;j<maxn;j++){
A[i][j]=mulmod(C[i][j],jc[j]);
}
}
for(rg int i=0;i<=c01;i++){
for(rg int j=i;j<=c01;j++){
g1[i]=addmod(g1[i],mulmod(C[c01][j],mulmod(s[j][i],A[c00+c01-j][c01-j])));
}
}
for(rg int i=0;i<=c10;i++){
for(rg int j=i;j<=c10;j++){
g2[i]=addmod(g2[i],mulmod(C[c10][j],mulmod(s[j][i],A[c00+c10-j][c10-j])));
}
}
for(rg int i=0;i<=c01;i++){
for(rg int j=i;j<=c01;j++){
if((j-i)&1) f1[i]=delmod(f1[i],mulmod(C[j][i],g1[j]));
else f1[i]=addmod(f1[i],mulmod(C[j][i],g1[j]));
}
}
for(rg int i=0;i<=c10;i++){
for(rg int j=i;j<=c10;j++){
if((j-i)&1) f2[i]=delmod(f2[i],mulmod(C[j][i],g2[j]));
else f2[i]=addmod(f2[i],mulmod(C[j][i],g2[j]));
}
}
for(rg int i=0;i<=c00;i++){
f3[i]=mulmod(s[c00][i],jc[c00]);
}
for(rg int i=0;i<=c01;i++){
for(rg int j=0;j<=c10;j++){
tmp[i+j]=addmod(tmp[i+j],mulmod(f1[i],f2[j]));
}
}
for(rg int i=0;i<=c01+c10;i++){
for(rg int j=0;j<=c00;j++){
ans[i+j]=addmod(ans[i+j],mulmod(tmp[i],f3[j]));
}
}
for(rg int i=0;i<n;i++){
if(n-i<cir) printf("0 ");
else printf("%d ",ans[n-i-cir]);
}
printf("
");
return 0;
}
B. girls
分析
考虑容斥,那么答案就是至少有 (0) 对点冲突的 (-) 至少有 (1) 对点冲突的 (+) 至少有 (2) 对点冲突的 (-) 至少有 (3) 对点冲突的
第一部分要求的就是 (sumlimits_{i=0}^{n-1}sumlimits_{j=i+1}^{n-1}sumlimits_{k=j+1}^{n-1}Ai+Bj+Ck),随便推一下式子就行了
第二部分考虑枚举每一条边作为冲突的一对点,这一对点可以和其它所有点组成三元组
第三部分考虑枚举每一个点,从这个点能够到达的所有点中选出两个计算贡献,可以用前缀和优化
第四步就是找出图中所有的三元环,这东西有一个 (msqrt{m}) 的做法
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<unordered_map>
#include<vector>
#include<cmath>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=2e5+5;
typedef unsigned long long ull;
int n,m,A,B,C,du[maxn];
ull ans;
ull getsum2(rg int val){
if(val<0) return 0;
return 1ull*(val+1)*val/2;
}
ull getsum3(rg int val){
if(val<0) return 0;
return 1ull*(val+1)*(2ull*val+1)*val/6;
}
std::vector<int> g[maxn],g2[maxn];
std::unordered_map<int,int> mp[maxn];
ull js0(){
rg ull nans=0;
for(rg int i=0;i<n;i++){
nans+=1ull*A*i*getsum2(n-i-2);
nans+=1ull*B*(n-1)*(getsum2(n-1)-getsum2(i));
nans-=1ull*B*(getsum3(n-1)-getsum3(i));
nans+=1ull*C*(n-1-i)*getsum2(n-1);
nans-=1ull*C*((getsum3(n-1)-getsum3(i)+getsum2(n-1)-getsum2(i))/2ull);
}
return nans;
}
ull js1(){
rg ull nans=0;
rg int aa;
for(rg int i=0;i<n;i++){
for(rg int j=0;j<g[i].size();j++){
aa=g[i][j];
if(i>aa) continue;
nans+=(1ull*A*getsum2(i-1)+1ull*B*i*i+1ull*C*aa*i);
nans+=(1ull*A*i*(aa-i-1)+1ull*B*(getsum2(aa-1)-getsum2(i))+1ull*C*aa*(aa-i-1));
nans+=(1ull*A*i*(n-1-aa)+1ull*B*aa*(n-1-aa)+1ull*C*(getsum2(n-1)-getsum2(aa)));
}
}
return nans;
}
ull sum[maxn];
ull js(rg int aa,rg int bb,rg int cc){
if(aa>bb) std::swap(aa,bb);
if(aa>cc) std::swap(aa,cc);
if(bb>cc) std::swap(bb,cc);
return 1ull*aa*A+1ull*bb*B+1ull*cc*C;
}
ull js2(){
rg ull nans=0;
rg int aa,bb,cc;
for(rg int i=0;i<n;i++){
sum[0]=0,bb=0,cc=g[i].size();
for(rg int j=1;j<=cc;j++){
sum[j]=sum[j-1]+g[i][j-1];
}
for(rg int j=1;j<=cc;j++){
if(g[i][j-1]>i) break;
bb=j;
}
nans+=1ull*A*sum[bb]*(cc-bb)+1ull*B*i*bb*(cc-bb)+1ull*C*(sum[cc]-sum[bb])*bb;
for(rg int j=1;j<=g[i].size();j++){
aa=g[i][j-1];
if(j<=bb) nans+=1ull*A*aa*(bb-j)+1ull*B*(sum[bb]-sum[j])+1ull*C*i*(bb-j);
else nans+=1ull*A*i*(cc-j)+1ull*B*aa*(cc-j)+1ull*C*(sum[cc]-sum[j]);
}
}
return nans;
}
int sta[maxn],tp;
ull js3(){
rg ull nans=0;
rg int aa,bb,sqr=sqrt(m);
for(rg int i=0;i<n;i++){
if(du[i]>sqr) sta[++tp]=i;
else {
for(rg int j=0;j<g[i].size();j++){
aa=g[i][j];
if(du[aa]>du[i] || (du[aa]==du[i] && aa>i)) g2[i].push_back(aa);
}
}
}
for(rg int i=0;i<n;i++){
if(du[i]<=sqr){
for(rg int j=0;j<g2[i].size();j++){
for(rg int k=j+1;k<g2[i].size();k++){
aa=g2[i][j],bb=g2[i][k];
if(mp[aa].find(bb)!=mp[aa].end()) nans+=js(i,aa,bb);
}
}
}
}
for(rg int i=1;i<=tp;i++){
for(rg int j=i+1;j<=tp;j++){
if(mp[sta[i]].find(sta[j])==mp[sta[i]].end()) continue;
for(rg int k=j+1;k<=tp;k++){
if(mp[sta[j]].find(sta[k])==mp[sta[j]].end()) continue;
if(mp[sta[i]].find(sta[k])==mp[sta[i]].end()) continue;
nans+=js(sta[i],sta[j],sta[k]);
}
}
}
return nans;
}
void solve(){
rg int aa,bb;
for(rg int i=1;i<=m;i++){
aa=read(),bb=read();
g[aa].push_back(bb),g[bb].push_back(aa);
mp[aa][bb]=mp[bb][aa]=1;
du[aa]++,du[bb]++;
}
for(rg int i=0;i<n;i++) std::sort(g[i].begin(),g[i].end());
ans=js0()-js1()+js2()-js3();
printf("%llu
",ans);
}
int main(){
n=read(),m=read(),A=read(),B=read(),C=read();
solve();
return 0;
}
C. string
分析
求一个子串出现的次数其实就是这个子串在后缀自动机中的子树和
因为强制在线,所以要用 (lct)
子树信息不好处理,可以改为链加和单点查询
对于第一种操作,开一个 (vector) 存储每次加入一个字符后,当前字符串所处的 (endpos) 集合,查询的时候二分查找即可
对于第二、第四种操作,因为 (n) 很小所以可以用一个数组维护每一个 (endpos) 集合中每一个字符串出现的次数
对于第三种操作,直接维护一个 (sum len[i]-len[fa[i]]) 即可
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<map>
#include<vector>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=4e5+5,maxm=1e7+5;
int n,m,t,totlen[maxn],latans;
struct jie{
int tim,num;
jie(){}
jie(rg int aa,rg int bb){
tim=aa,num=bb;
}
friend bool operator <(const jie& A,const jie& B){
return A.tim<B.tim;
}
};
std::vector<jie> g[maxn];
char s[25][maxn],ss[maxm];
struct LCT{
int val[25][maxn],fa[maxn],ch[maxn][2],tag[25][maxn],rev[maxn],sta[maxn],tp;
void push_down(rg int da){
rg int lc=ch[da][0],rc=ch[da][1];
for(rg int i=1;i<=n;i++){
if(tag[i][da]){
if(lc) tag[i][lc]+=tag[i][da],val[i][lc]+=tag[i][da];
if(rc) tag[i][rc]+=tag[i][da],val[i][rc]+=tag[i][da];
tag[i][da]=0;
}
}
if(rev[da]){
rev[lc]^=1,rev[rc]^=1,rev[da]^=1;
std::swap(ch[da][0],ch[da][1]);
}
}
bool isroot(rg int da){
return ch[fa[da]][0]!=da&&ch[fa[da]][1]!=da;
}
void xuanzh(rg int x){
rg int y=fa[x];
rg int z=fa[y];
rg int k=(ch[y][1]==x);
if(!isroot(y)) ch[z][ch[z][1]==y]=x;
fa[x]=z;
ch[y][k]=ch[x][k^1];
fa[ch[x][k^1]]=y;
ch[x][k^1]=y;
fa[y]=x;
}
void splay(rg int x){
sta[tp=1]=x;
for(rg int i=x;!isroot(i);i=fa[i]) sta[++tp]=fa[i];
for(rg int i=tp;i>=1;i--) push_down(sta[i]);
while(!isroot(x)){
rg int y=fa[x];
rg int z=fa[y];
if(!isroot(y)) (ch[z][1]==y)^(ch[y][1]==x)?xuanzh(x):xuanzh(y);
xuanzh(x);
}
}
void access(rg int x){
for(rg int y=0;x;y=x,x=fa[x]){
splay(x);
ch[x][1]=y;
}
}
void makeroot(rg int x){
access(x);
splay(x);
rev[x]^=1;
push_down(x);
}
void split(rg int x,rg int y){
makeroot(x);
access(y);
splay(y);
}
void link(rg int x,rg int y){
makeroot(x);
fa[x]=y;
}
void cut(rg int x,rg int y){
split(x,y);
ch[y][0]=fa[x]=0;
}
}lct;
struct SAM{
int ch[maxn][12],fa[maxn],len[maxn],cnt;
long long tot;
int insert(rg int lst,rg int c){
rg int p=lst;
if(ch[p][c]){
rg int q=ch[p][c];
if(len[q]==len[p]+1) return q;
else {
rg int nq=++cnt;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
lct.splay(q);
for(rg int i=1;i<=n;i++){
lct.val[i][nq]=lct.val[i][q];
}
lct.cut(q,fa[q]);
fa[nq]=fa[q];
fa[q]=nq;
lct.link(q,fa[q]);
lct.link(nq,fa[nq]);
for(;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
return nq;
}
}
rg int np=lst=++cnt;
len[np]=len[p]+1;
for(;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) {
fa[np]=1;
lct.link(np,fa[np]);
} else {
rg int q=ch[p][c];
if(len[q]==len[p]+1){
fa[np]=q;
lct.link(np,fa[np]);
} else {
rg int nq=++cnt;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
lct.splay(q);
for(rg int i=1;i<=n;i++){
lct.val[i][nq]=lct.val[i][q];
}
lct.cut(q,fa[q]);
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
lct.link(q,fa[q]);
lct.link(nq,fa[nq]);
lct.link(np,fa[np]);
for(;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
tot+=len[np]-len[fa[np]];
return np;
}
void build(){
cnt=1;
for(rg int o=1,lst=1;o<=n;o++){
lst=1;
for(rg int i=1;i<=totlen[o];i++){
lst=insert(lst,s[o][i]-'0');
lct.split(lst,1);
lct.tag[o][1]++,lct.val[o][1]++;
}
g[o].push_back(jie(0,lst));
}
}
void ad(rg int op,rg int p,rg int id){
rg int lst=g[op][g[op].size()-1].num;
s[op][totlen[op]]=p+'0';
lst=insert(lst,s[op][totlen[op]]-'0');
lct.split(lst,1);
lct.tag[op][1]++,lct.val[op][1]++;
g[op].push_back(jie(id,lst));
}
int cx1(rg int x,rg int y,rg int z){
rg int wz=std::upper_bound(g[x].begin(),g[x].end(),jie(y,0))-g[x].begin()-1;
wz=g[x][wz].num;
lct.splay(wz);
return lct.val[z][wz];
}
long long cx2(){
return tot;
}
int cx3(){
scanf("%s",ss+1);
rg int cs=0,now=1,nlen=strlen(ss+1);
for(rg int i=1;i<=nlen;i++){
rg int p=ss[i]-'0';
while(now && !ch[now][p]) now=fa[now],cs=len[now];
if(!now){
now=1,cs=0;
} else {
now=ch[now][p],cs++;
}
}
if(cs<nlen) return 0;
while(len[fa[now]]>=nlen) now=fa[now];
rg int mmax=0;
lct.splay(now);
for(rg int i=1;i<=n;i++) mmax=std::max(mmax,lct.val[i][now]);
return mmax;
}
}sam;
int main(){
n=read(),t=read();
for(rg int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(rg int i=1;i<=n;i++) totlen[i]=strlen(s[i]+1);
sam.build();
m=read();
rg int op,x,y,z;
for(rg int i=1;i<=m;i++){
op=read();
if(op==1){
x=read(),y=read();
if(t) y=(y^latans)%10;
totlen[x]++;
sam.ad(x,y,i);
} else if(op==2){
x=read(),y=read(),z=read();
latans=sam.cx1(x,y,z);
printf("%d
",latans);
} else if(op==3){
printf("%lld
",sam.cx2());
} else {
latans=sam.cx3();
printf("%d
",latans);
}
}
return 0;
}