【考后总结】7 月 NOI 模拟赛 1
7.2 冲刺国赛自测 9
T1 字符串
一个合法位置 \([l,r]\) 代表 \([1,x]\) 与 \([l,l+x-1]\) 相同,\([y,n]\) 与 \([r-y+1,r]\) 相同,类似 \(x\in \mathrm{Border}(l+x-1)\)。
对正反串做 KMP,建失配树,类似要求 \(x\) 子树和 \(y\) 子树的交,而 \((l+x-1)+1=(r-y+1)\) 所以正串失配树子树里节点 \(u\) 对应反串失配树子树里节点 \(n-u\),二维数点即可。
点击查看代码
int rt[maxn];
struct SegmentTree{
#define mid ((l+r)>>1)
int ch[maxn*40][2],tot;
int siz[maxn*40];
inline void clear(){
tot=0;
memset(ch,0,sizeof(ch));
memset(siz,0,sizeof(siz));
}
inline void new_node(int &pos){
ch[++tot][0]=ch[pos][0],ch[tot][1]=ch[pos][1],siz[tot]=siz[pos];
pos=tot;
}
void insert(int &pos,int l,int r,int p){
new_node(pos);
++siz[pos];
if(l==r) return;
if(p<=mid) insert(ch[pos][0],l,mid,p);
else insert(ch[pos][1],mid+1,r,p);
}
int query(int lpos,int rpos,int l,int r,int pl,int pr){
if(pl<=l&&r<=pr) return siz[rpos]-siz[lpos];
int res=0;
if(pl<=mid) res+=query(ch[lpos][0],ch[rpos][0],l,mid,pl,pr);
if(pr>mid) res+=query(ch[lpos][1],ch[rpos][1],mid+1,r,pl,pr);
return res;
}
#undef mid
}S;
int t;
int n,q;
char s1[maxn],s2[maxn];
int nxt1[maxn],nxt2[maxn];
vector<int> E1[maxn],E2[maxn];
int fa1[maxn],siz1[maxn],dfn1[maxn],dfncnt1;
int fa2[maxn],siz2[maxn],dfn2[maxn],dfncnt2;
int id[maxn];
inline void get_nxt(){
nxt1[1]=0;
E1[0].push_back(1);
for(int i=2,j=0;i<=n;++i){
while(j&&s1[i]!=s1[j+1]) j=nxt1[j];
if(s1[i]==s1[j+1]) ++j;
nxt1[i]=j;
E1[j].push_back(i);
}
nxt2[1]=0;
E2[0].push_back(1);
for(int i=2,j=0;i<=n;++i){
while(j&&s2[i]!=s2[j+1]) j=nxt2[j];
if(s2[i]==s2[j+1]) ++j;
nxt2[i]=j;
E2[j].push_back(i);
}
}
void dfs1(int u,int f){
fa1[u]=f,siz1[u]=1;
if(u) dfn1[u]=++dfncnt1,id[dfn1[u]]=u;
for(int v:E1[u]){
if(v==f) continue;
dfs1(v,u);
siz1[u]+=siz1[v];
}
}
void dfs2(int u,int f){
fa2[u]=f,siz2[u]=1;
if(u) dfn2[u]=++dfncnt2;
for(int v:E2[u]){
if(v==f) continue;
dfs2(v,u);
siz2[u]+=siz2[v];
}
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
t=read();
while(t--){
n=read(),q=read();
for(int i=0;i<=n;++i){
nxt1[i]=nxt2[i]=0;
E1[i].clear(),E2[i].clear();
dfncnt1=dfncnt2=0;
fa1[i]=siz1[i]=dfn1[i]=fa2[i]=siz2[i]=dfn2[i]=0;
}
S.clear();
scanf("%s",s1+1);
for(int i=1;i<=n;++i) s2[i]=s1[n-i+1];
get_nxt();
dfs1(0,0);
dfs2(0,0);
rt[0]=0;
for(int i=1;i<=n;++i){
int j=dfn2[n-id[i]];
rt[i]=rt[i-1];
if(j) S.insert(rt[i],1,n,j);
}
while(q--){
int x=read(),y=read();
printf("%d\n",S.query(rt[dfn1[x]-1],rt[dfn1[x]+siz1[x]-1],1,n,dfn2[y],dfn2[y]+siz2[y]-1));
}
}
return 0;
}
T2 计树
魔幻转移是枚举 \(x\in [1,n)\),令 \(p_i=[p_i>x]\),求所有 \(p\) 中相邻位置不同的个数和就是答案,正确性在于 \(p_i=a,p_{i+1}=b\ (a<b)\) 时,\(p_i\neq p_{i+1}\) 恰好为 \(x\in [a,b)\),贡献就是 \(b-a\)。
考虑 DP,目标是子树内构成 \(p\) 的子序列中某两个相邻不同的方案数,同时需要维护出每个位置上 \(0/1\) 的方案数,也需要维护出子树内方案总数。
设 \(f_u\) 为 \(u\) 子树内合法 \(p\) 序列方案数,\(g_{u,k,0/1}\) 为 \(u\) 子树内 \(p_k=0/1\) 的方案数,\(h_{u,k}\) 为 \(u\) 子树内 \(p_k\neq p_{k+1}\) 的方案数。
\(f\) 转移不断乘 \(f_v\) 和组合数即可。
\(g\) 转移讨论 \(k\) 来自已经合并过的子树或是 \(v\),其余位置的排布方案乘组合数。
\(h\) 讨论同理,更复杂一点需要讨论 \(k\) 与 \(k+1\) 分别来自哪里,方案依旧是组合数。
转移精细实现复杂度是树上背包 \(O(n^2)\)。
根据题意,\(p\) 序列第一个元素一定是 \(u\),所以 \(p_1\) 实际根据 \(x\) 固定,最后把 \(u\) 加进去时先向右平移,再算 \(1\) 处值即可。
点击查看代码
inline int q_pow(int A,int B,int P){
int res=1;
while(B){
if(B&1) res=1ll*res*A%P;
A=1ll*A*A%P;
B>>=1;
}
return res;
}
int n,rt;
vector<int> E[maxn];
int fact[maxn],inv_fact[maxn];
inline int C(int N,int M){
if(N<M) return 0;
return 1ll*fact[N]*inv_fact[M]%mod*inv_fact[N-M]%mod;
}
int siz[maxn];
int f[maxn],g[maxn][maxn][2],h[maxn][maxn],tmpg[maxn][2],tmph[maxn];
void dfs(int u,int fa,int x){
f[u]=1;
for(int v:E[u]){
if(v==fa) continue;
dfs(v,u,x);
for(int i=1;i<=siz[u]+siz[v];++i) tmpg[i][0]=tmpg[i][1]=0,tmph[i]=0;
for(int i=0;i<=siz[u];++i){
for(int j=0;j<=siz[v];++j){
if(i){
tmpg[i+j][0]=(tmpg[i+j][0]+1ll*g[u][i][0]*f[v]%mod*C(i+j-1,j)%mod*C(siz[u]+siz[v]-(i+j),siz[v]-j)%mod)%mod;
tmpg[i+j][1]=(tmpg[i+j][1]+1ll*g[u][i][1]*f[v]%mod*C(i+j-1,j)%mod*C(siz[u]+siz[v]-(i+j),siz[v]-j)%mod)%mod;
}
if(j){
tmpg[i+j][0]=(tmpg[i+j][0]+1ll*f[u]*g[v][j][0]%mod*C(i+j-1,j-1)%mod*C(siz[u]+siz[v]-(i+j),siz[v]-j)%mod)%mod;
tmpg[i+j][1]=(tmpg[i+j][1]+1ll*f[u]*g[v][j][1]%mod*C(i+j-1,j-1)%mod*C(siz[u]+siz[v]-(i+j),siz[v]-j)%mod)%mod;
}
}
}
for(int i=0;i<=siz[u];++i){
for(int j=0;j<=siz[v];++j){
if(i&&i<siz[u]){
tmph[i+j]=(tmph[i+j]+1ll*h[u][i]*f[v]%mod*C(i+j-1,j)%mod*C(siz[u]+siz[v]-(i+j+1),siz[v]-j)%mod)%mod;
}
if(j&&j<siz[v]){
tmph[i+j]=(tmph[i+j]+1ll*f[u]*h[v][j]%mod*C(i+j-1,j-1)%mod*C(siz[u]+siz[v]-(i+j+1),siz[v]-(j+1))%mod)%mod;
}
if(i&&j<siz[v]){
tmph[i+j]=(tmph[i+j]+1ll*g[u][i][0]*g[v][j+1][1]%mod*C(i+j-1,j)%mod*C(siz[u]+siz[v]-(i+j+1),siz[v]-(j+1))%mod)%mod;
tmph[i+j]=(tmph[i+j]+1ll*g[u][i][1]*g[v][j+1][0]%mod*C(i+j-1,j)%mod*C(siz[u]+siz[v]-(i+j+1),siz[v]-(j+1))%mod)%mod;
}
if(i<siz[u]&&j){
tmph[i+j]=(tmph[i+j]+1ll*g[u][i+1][0]*g[v][j][1]%mod*C(i+j-1,j-1)%mod*C(siz[u]+siz[v]-(i+j+1),siz[v]-j)%mod)%mod;
tmph[i+j]=(tmph[i+j]+1ll*g[u][i+1][1]*g[v][j][0]%mod*C(i+j-1,j-1)%mod*C(siz[u]+siz[v]-(i+j+1),siz[v]-j)%mod)%mod;
}
}
}
f[u]=1ll*f[u]*f[v]%mod*C(siz[u]+siz[v],siz[v])%mod;
siz[u]+=siz[v];
for(int i=1;i<=siz[u];++i) g[u][i][0]=tmpg[i][0],g[u][i][1]=tmpg[i][1],h[u][i]=tmph[i];
}
++siz[u];
for(int i=siz[u];i>1;--i) g[u][i][0]=g[u][i-1][0],g[u][i][1]=g[u][i-1][1],h[u][i]=h[u][i-1];
int now=(u>x);
g[u][1][0]=g[u][1][1]=h[u][1]=0;
g[u][1][now]=f[u];
h[u][1]=g[u][2][now^1];
}
int ans;
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read(),rt=read();
fact[0]=1;
for(int i=1;i<=n;++i) fact[i]=1ll*fact[i-1]*i%mod;
inv_fact[0]=1,inv_fact[n]=q_pow(fact[n],mod-2,mod);
for(int i=n-1;i>=1;--i) inv_fact[i]=1ll*inv_fact[i+1]*(i+1)%mod;
for(int i=1;i<n;++i){
int u=read(),v=read();
E[u].push_back(v);
E[v].push_back(u);
}
for(int x=1;x<n;++x){
memset(siz,0,sizeof(siz));
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
memset(h,0,sizeof(h));
dfs(rt,0,x);
for(int i=1;i<n;++i) ans=(ans+h[rt][i])%mod;
}
printf("%d\n",ans);
return 0;
}
T3 数论
不妨把每个 \(p_i\) 和 \(a_j\) 全部拆开,改为 \(p_i\) 在 \(a_j\) 的次数是 \(c_{i,j}\) 且 \(\sum_{i,j} c_{i,j}=N\)。
答案就可以写成:
\(\sum_{i,j} c_{i,j}=N\) 容易想到生成函数,那么 \(F_i(x)\) 对应质数 \(p_i\),则:
求封闭形式,得到:
最终要求:
可以分治乘+快速幂,最后得到一个形如 \([x^N]\dfrac{f(x)}{g(x)}\) 的结果,Bostan-Mori 算法解决,复杂度 \(O(n\log n\log N)\)。
点击查看代码
inline int q_pow(int A,int B,int P){
int res=1;
while(B){
if(B&1) res=1ll*res*A%P;
A=1ll*A*A%P;
B>>=1;
}
return res;
}
int rev[maxn];
int base,w[maxn];
struct Poly{
const static int g=3;
const static int inv_g=332748118;
int deg;
vector<ull> f;
ull& operator[](const int &i){return f[i];}
ull operator[](const int &i)const{return f[i];}
inline void set(int L){deg=L;f.resize(L);}
inline void clear(int L,int R){for(int i=L;i<=R;++i)f[i]=0;}
inline void output(int L){for(int i=0;i<L;++i)printf("%llu ",f[i]);printf("\n");}
inline void NTT(int L,bool type){
set(L);
for(int i=1;i<L;++i){
rev[i]=(rev[i>>1]>>1)+(i&1?L>>1:0);
if(i<rev[i]) swap(f[i],f[rev[i]]);
}
for(int d=1;d<L;d<<=1){
base=q_pow(type?g:inv_g,(mod-1)/(2*d),mod);
w[0]=1;
for(int i=1;i<d;++i) w[i]=1ll*w[i-1]*base%mod;
for(int i=0;i<L;i+=d<<1){
for(int j=0;j<d;++j){
ull x=f[i+j],y=f[i+d+j]*w[j]%mod;
f[i+j]=x+y,f[i+d+j]=x-y+mod;
}
}
}
for(int i=0;i<L;++i) f[i]%=mod;
if(!type){
int inv_L=q_pow(L,mod-2,mod);
for(int i=0;i<L;++i) f[i]=f[i]*inv_L%mod;
}
}
inline Poly Pow(int k){
Poly res=(*this),base=(*this);
--k;
int L=deg;
while(k){
L<<=1;
base.NTT(L,1);
if(k&1){
res.NTT(L,1);
for(int i=0;i<L;++i) res[i]=res[i]*base[i]%mod;
res.NTT(L,0);
}
for(int i=0;i<L;++i) base[i]=base[i]*base[i]%mod;
base.NTT(L,0);
k>>=1;
}
return res;
}
};
int a[maxn];
Poly Conquer(int l,int r){
if(l==r){
Poly F;
F.set(2);
F[0]=1ull,F[1]=(ull)mod-a[l];
return F;
}
int mid=(l+r)>>1;
Poly F=Conquer(l,mid),G=Conquer(mid+1,r);
int L=1;
while(L<=(r-l+1)) L<<=1;
Poly H;
H.set(L);
F.NTT(L,1),G.NTT(L,1);
for(int i=0;i<L;++i) H[i]=F[i]*G[i]%mod;
H.NTT(L,0);
return H;
}
inline void Bostan_Mori(int N,Poly F,Poly G){
int L=F.deg;
Poly H,A,B;
F.set(L<<1),G.set(L<<1),H.set(L<<1),A.set(L<<1),B.set(L<<1);
while(N){
H=G;
for(int i=1;i<L;i+=2) H[i]=mod-H[i];
F.NTT(L<<1,1),G.NTT(L<<1,1),H.NTT(L<<1,1);
for(int i=0;i<L<<1;++i) A[i]=F[i]*H[i]%mod,B[i]=G[i]*H[i]%mod;
A.NTT(L<<1,0),B.NTT(L<<1,0);
for(int i=0;i<L;++i) A[i]=A[2*i+(N&1)];
for(int i=0;i<L;++i) B[i]=B[2*i];
A.clear(L,(L<<1)-1),B.clear(L,(L<<1)-1);
F=A,G=B;
N>>=1;
}
printf("%llu\n",F[0]*q_pow((int)G[0],mod-2,mod)%mod);
}
int N,n,m;
int p[maxn];
int main(){
freopen("math.in","r",stdin);
freopen("math.out","w",stdout);
N=read(),n=read(),m=read();
for(int i=1;i<=n;++i) p[i]=read();
Poly F,G;
for(int i=1;i<=n;++i) a[i]=1;
F=Conquer(1,n);
F=F.Pow(m);
for(int i=1;i<=n;++i) a[i]=p[i];
G=Conquer(1,n);
G=G.Pow(m);
Bostan_Mori(N,F,G);
return 0;
}
7.6 冲刺国赛模拟 31
T1 标志
赛时写的分类讨论,写挂一车。
可以 \(O(n^6)\) 枚举三个 L
的拐点位置。计算方案数考虑容斥,手动把式子拆开算。
点击查看代码
int n,m;
char s[maxn][maxn];
int sumr[maxn][maxn],sumc[maxn][maxn];
ll ans;
int main(){
freopen("logo.in","r",stdin);
freopen("logo.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;++i){
scanf("%s",s[i]+1);
}
for(int i=1;i<=n;++i){
for(int j=m;j>=1;--j){
if(s[i][j]=='.') sumr[i][j]=sumr[i][j+1]+1;
else sumr[i][j]=0;
}
}
for(int j=1;j<=m;++j){
for(int i=1;i<=n;++i){
if(s[i][j]=='.') sumc[i][j]=sumc[i-1][j]+1;
else sumc[i][j]=0;
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(s[i][j]=='.') --sumr[i][j],--sumc[i][j];
}
}
for(int x1=1;x1<=n;++x1){
for(int y1=1;y1<=m;++y1){
for(int x2=x1;x2<=n;++x2){
for(int y2=1;y2<=m;++y2){
for(int x3=x2;x3<=n;++x3){
for(int y3=1;y3<=m;++y3){
if(s[x1][y1]=='#'||s[x2][y2]=='#'||s[x3][y3]=='#') continue;
if(!sumr[x1][y1]||!sumc[x1][y1]||!sumr[x2][y2]||!sumc[x2][y2]||!sumr[x3][y3]||!sumc[x3][y3]) continue;
if(x1==x2&&y1>=y2) continue;
if(x1==x3&&y1>=y3) continue;
if(x2==x3&&y2>=y3) continue;
ll now;
int r1=sumr[x1][y1],c1=sumc[x1][y1],r2=sumr[x2][y2],c2=sumc[x2][y2],r3=sumr[x3][y3],c3=sumc[x3][y3];
if(x1==x2&&x2==x3) now=1ll*min(r1,y2-y1-1)*c1*min(r2,y3-y2-1)*c2*r3*c3;
else if(x1==x2){
if(y3==y1) now=1ll*min(r1,y2-y1-1)*c1*r2*c2*r3*min(c3,x3-x1-1);
else if(y3==y2) now=1ll*min(r1,y2-y1-1)*c1*r2*c2*r3*min(c3,x3-x2-1);
else if(y3<y1) now=1ll*min(r1,y2-y1-1)*c1*r2*c2*r3*c3;
else if(y1<y3&&y3<y2) now=1ll*c1*r2*c2*r3*(min(r1,y2-y1-1)*c3-max((y1+min(r1,y2-y1-1))-y3+1,0)*max(x1-(x3-c3)+1,0));
else if(y3>y2) now=1ll*min(r1,y2-y1-1)*c1*c2*r3*(r2*c3-max((y2+r2)-y3+1,0)*max(x2-(x3-c3)+1,0));
}
else if(x2==x3){
if(y1==y2) now=1ll*c1*min(r2,y3-y2-1)*min(c2,x2-x1-1)*r3*(r1*c3-max((y1+r1)-y3+1,0)*max(x1-(x3-c3)+1,0));
else if(y1==y3) now=1ll*r1*c1*min(r2,y3-y2-1)*c2*r3*min(c3,x3-x1-1);
else if(y1<y2){
now=1ll*c1*min(r2,y3-y2-1)*r3*(max((y1+r1)-y3+1,0)*min(c2,x2-x1-1)*min(c3,x3-x1-1)
+max((y1+min(r1,y3-y1-1))-y2+1,0)*min(c2,x2-x1-1)*c3
+min(r1,y2-y1-1)*c2*c3);
}
else if(y2<y1&&y1<y3) now=1ll*c1*min(r2,y3-y2-1)*c2*r3*(r1*c3-max((y1+r1)-y3+1,0)*max(x1-(x3-c3)+1,0));
else if(y1>y3) now=1ll*r1*c1*min(r2,y3-y2-1)*c2*r3*c3;
}
else{
if(y1==y2&&y2==y3) now=1ll*r1*c1*r2*min(c2,x2-x1-1)*r3*min(c3,x3-x2-1);
else if(y1==y2){
if(y3<y1) now=1ll*r1*c1*r2*min(c2,x2-x1-1)*r3*c3;
else if(y3>y1){
now=1ll*c1*min(c2,x2-x1-1)*r3*(max(x1-(x3-c3)+1,0)*min(r1,y3-y1-1)*min(r2,y3-y2-1)
+max(x2-(x3-min(c3,x3-x1-1))+1,0)*r1*min(r2,y3-y2-1)
+min(c3,x3-x2-1)*r1*r2);
}
}
else if(y1==y3){
if(y2<y1) now=1ll*r1*c1*c2*r3*(r2*min(c3,x3-x1-1)-max((y2+r2)-y3+1,0)*max(x2-(x3-min(c3,x3-x1-1))+1,0));
else if(y2>y1) now=1ll*c1*r2*r3*min(c3,x3-x1-1)*(r1*c2-max((y1+r1)-y2+1,0)*max(x1-(x2-c2)+1,0));
}
else if(y2==y3){
if(y1<y2) now=1ll*c1*r2*r3*min(c3,x3-x2-1)*(r1*c2-max((y1+r1)-y2+1,0)*max(x1-(x2-c2)+1,0));
else if(y1>y2) now=1ll*r1*c1*r2*c2*r3*min(c3,x3-x2-1);
}
else{
if(y1<y2&&y2<y3){
now=1ll*c1*r3*(r1*r2*c2*c3
-r2*c3*max((y1+r1)-y2+1,0)*max(x1-(x2-c2)+1,0)
-r1*c2*max((y2+r2)-y3+1,0)*max(x2-(x3-c3)+1,0)
-r2*c2*max((y1+r1)-y3+1,0)*max(x1-(x3-c3)+1,0)
+r2*max((y1+r1)-y3+1,0)*max(x1-(x2-c2)+1,0)*max(x1-(x3-c3)+1,0)
+max((y1+r1)-y2+1,0)*max(x1-(x2-c2)+1,0)*max((y2+r2)-y3+1,0)*max(x2-(x3-c3)+1,0)
+c2*max((y1+r1)-y3+1,0)*max((y2+r2)-y3+1,0)*max(x1-(x3-c3)+1,0)
-max(x1-(x2-c2)+1,0)*max(x1-(x3-c3)+1,0)*max((y1+r1)-y3+1,0)*max((y2+r2)-y3+1,0));
}
else if(y1<y3&&y3<y2){
now=1ll*c1*r2*r3*(max((y1+r1)-y2+1,0)*min(c3,x3-x1-1)*min(c2,x2-x1-1)
+max((y1+min(r1,y2-y1-1))-y3+1,0)*min(c3,x3-x1-1)*c2
+min(r1,y3-y1-1)*c3*c2);
}
else if(y2<y1&&y1<y3){
now=1ll*c1*c2*r3*(max(x1-(x3-c3)+1,0)*min(r1,y3-y1-1)*min(r2,y3-y2-1)
+max(x2-(x3-min(c3,x3-x1-1))+1,0)*r1*min(r2,y3-y2-1)
+min(c3,x3-x2-1)*r1*r2);
}
else if(y2<y3&&y3<y1) now=1ll*r1*c1*c2*r3*(r2*c3-max((y2+r2)-y3+1,0)*max(x2-(x3-c3)+1,0));
else if(y3<y1&&y1<y2) now=1ll*c1*r2*r3*c3*(r1*c2-max((y1+r1)-y2+1,0)*max(x1-(x2-c2)+1,0));
else now=1ll*r1*c1*r2*c2*r3*c3;
}
}
ans+=now;
}
}
}
}
}
}
printf("%lld\n",ans);
return 0;
}
正解是插头 DP。
注意到插头只有三个,直接 \(O(nm^3)\),状态记录上插头位置以及是否有左插头即可。
点击查看代码
int n,m;
char mp[35][35];
int ex,ey;
int id[35][35][35],cnt;
struct Data{
int a,b,c;
Data()=default;
Data(int a_,int b_,int c_):a(a_),b(b_),c(c_){}
}S[31*31*31+10];
inline int get_id(int a,int b,int c){
int cnt0=0;
if(!a) ++cnt0;
if(!b) ++cnt0;
if(!c) ++cnt0;
if(cnt0==3) return id[0][0][0];
else if(cnt0==2) return id[max({a,b,c})][0][0];
else if(cnt0==1) return id[a+b+c-max({a,b,c})][max({a,b,c})][0];
else return id[min({a,b,c})][a+b+c-max({a,b,c})-min({a,b,c})][max({a,b,c})];
}
ll dp[2][31*31*31+10][2][4],ans;
int main(){
freopen("logo.in","r",stdin);
freopen("logo.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;++i){
scanf("%s",mp[i]+1);
for(int j=1;j<=m;++j){
if(mp[i][j]=='.') ex=i,ey=j;
}
}
id[0][0][0]=++cnt,S[cnt]=Data(0,0,0);
for(int a=1;a<=m;++a) id[a][0][0]=++cnt,S[cnt]=Data(a,0,0);
for(int a=1;a<=m;++a){
for(int b=a+1;b<=m;++b){
id[a][b][0]=++cnt,S[cnt]=Data(a,b,0);
}
}
for(int a=1;a<=m;++a){
for(int b=a+1;b<=m;++b){
for(int c=b+1;c<=m;++c){
id[a][b][c]=++cnt,S[cnt]=Data(a,b,c);
}
}
}
int now=0;
dp[now][get_id(0,0,0)][0][0]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
now^=1;
for(int s=1;s<=cnt;++s){
for(int p=0;p<=1;++p){
for(int k=0;k<=3;++k){
dp[now][s][p][k]=0;
}
}
}
for(int s=1;s<=cnt;++s){
for(int k=0;k<=3;++k){
ll v0=dp[now^1][s][0][k],v1=dp[now^1][s][1][k];
if(!v0&&!v1) continue;
int a=S[s].a,b=S[s].b,c=S[s].c;
// cerr<<"i:"<<i<<" j:"<<j<<" a:"<<a<<" b:"<<b<<" c:"<<c<<" k:"<<k<<" 0:"<<v0<<" 1:"<<v1<<endl;
if(mp[i][j]=='#'){
if(j!=a&&j!=b&&j!=c) dp[now][s][0][k]+=v0;
}
else{
if(j!=a&&j!=b&&j!=c){
dp[now][s][0][k]+=v0+v1;
if(mp[i][j+1]=='.') dp[now][s][1][k]+=v1;
if(!c&&mp[i+1][j]=='.'&&k<3) dp[now][get_id(a,b,j)][0][k+1]+=v0;
if(i==ex&&j==ey&&k==3) ans+=v0+v1;
}
else{
if(mp[i+1][j]=='.') dp[now][s][0][k]+=v0;
if(mp[i][j+1]=='.') dp[now][get_id((j==a)?0:a,(j==b)?0:b,(j==c)?0:c)][1][k]+=v0;
}
}
}
}
}
}
printf("%lld\n",ans);
return 0;
}
7.7 冲刺国赛模拟 32
T1 树
考虑 DP,连通性使用经典容斥,设 \(f_{S,i}\) 为当前 \(1\) 所在连通块为 \(S\),且选中边数为 \(i\) 的方案数,设 \(cnt_S\) 为点集为 \(S\) 时可选的边数,转移减去不连通(一部分连通剩下边随便选)的情况:
这样复杂度 \(O(3^nm^2)\)。
观察这个转移方程,是一个子集卷积搭配多项式卷积的形式,考虑把 \(f_S\) 整体看作多项式,于是:
设 \(y=1+x\),后面就变成枚举子集后多项式平移。算出 \(y\) 的系数再把 \(1+x\) 代入展开即可,复杂度 \(O(3^nm+m^2)\)。
点击查看代码
int n,m;
pii e[maxm];
int cnt[maxs];
int C[maxm][maxm];
int f[maxs][maxm];
int ans[maxm];
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read(),m=read();
C[0][0]=1;
for(int i=1;i<=m;++i){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;++j){
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
for(int i=1;i<=m;++i){
e[i].fir=read()+1,e[i].sec=read()+1;
}
for(int s=0;s<(1<<n);++s){
for(int i=1;i<=m;++i){
int u=e[i].fir,v=e[i].sec;
if(s&(1<<u-1)&&s&(1<<v-1)) ++cnt[s];
}
}
for(int s=0;s<(1<<n);++s){
if(!(s&1)) continue;
f[s][cnt[s]]=1;
for(int t=s-1;t;t=(t-1)&s){
if(!(t&1)) continue;
for(int i=cnt[s^t];i<=cnt[s];++i){
f[s][i]=(f[s][i]-f[t][i-cnt[s^t]]+mod)%mod;
}
}
}
for(int i=0;i<=m;++i){
for(int j=0;j<=i;++j){
ans[j]=(ans[j]+1ll*f[(1<<n)-1][i]*C[i][j]%mod)%mod;
}
}
for(int i=n-1;i<=m;++i) printf("%d ",ans[i]);
printf("\n");
return 0;
}
T3 贼
以下是口胡题解。
答案是 \(\sum_{i=l}^r \min(\mathrm{lcp}(l,i),r-i+1)\),建反串 SAM 得到后缀树,这样 \(\mathrm{lcp}\) 就是后缀树上对应节点 \(\mathrm{LCA}\) 的 \(\mathrm{len}\) 值。
考虑点分治,\(l\) 与 \(i\) 对应节点在实际只有两种情况:\(i\) 是否在当前分治中心后缀树的子树内。如果在子树内,那么 \(\mathrm{LCA}\) 只和 \(l\) 有关,这样每次询问都是在数 \(i\) 的范围,线段树或树状数组即可;反之则只和 \(i\) 有关,这样数点就有 \(i\) 以及 \(i\) 与分治中心的 \(\mathrm{LCA}\) 有关,这是固定的,可以二维数点。
但是很难写,且空间大致要 1024MB 以上。