「题单题解」数数入门
Coffee_zzz · · 算法·理论
P1758 [NOI2009] 管道取珠
:::info[题解]
有一个经典的 trick 是,可以将每个东西出现次数的平方转化为选择两个相同东西的方案数。对于此题,即为将
考虑 dp:设
对第一维做滚动优化,时间复杂度
:::
:::success[代码]
const int N=505,mod=1024523;
int n,m,f[2][N][N];
string s,t;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>m>>s>>t;
f[0][0][0]=1;
for(int i=0;i<n+m;i++){
int x=i&1,y=(i&1)^1;
for(int j=0;j<=min(n,i);j++){
for(int k=0;k<=min(n,i);k++){
if(j<n&&k<n&&s[j]==s[k]) add(f[y][j+1][k+1],f[x][j][k]);
if(j<n&&i-k<m&&s[j]==t[i-k]) add(f[y][j+1][k],f[x][j][k]);
if(i-j<m&&k<n&&t[i-j]==s[k]) add(f[y][j][k+1],f[x][j][k]);
if(i-j<m&&i-k<m&&t[i-j]==t[i-k]) add(f[y][j][k],f[x][j][k]);
}
}
memset(f[x],0,sizeof f[x]);
}
cout<<f[(n+m)&1][n][n];
}
:::
CF509F Progress Monitoring
:::info[题解]
给出的序列
设
答案即为
:::
:::success[代码]
const int N=505,mod=1e9+7;
int n,b[N],f[N][N],g[N][N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>b[i],f[i][i]=g[i][i]=1;
for(int l=n;l>=1;l--){
for(int r=l+1;r<=n;r++){
f[l][r]=g[l][r]=g[l+1][r];
for(int k=l;k<r;k++) if(b[l]<b[k+1]) add(g[l][r],1ll*f[l][k]*g[k+1][r]%mod);
}
}
cout<<f[1][n]<<endl;
}
:::
CF449D Jzzhu and Numbers
:::info[题解]
直接计算按位与为
接下来考虑怎么计算
时间复杂度
:::
:::success[代码]
const int V=1<<20,N=V+5,mod=1e9+7;
int n,pw[N],c[N],f[N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void And(int V,int *f,int c){
for(int k=1;k<V;k<<=1){
for(int i=0;i<V;i++){
if(i&k) continue;
if(c) add(f[i],f[i^k]);
else add(f[i],mod-f[i^k]);
}
}
}
void solve(){
cin>>n;
for(int i=1,x;i<=n;i++) cin>>x,c[x]++;
And(V,c,1);
pw[0]=1;
for(int i=1;i<V;i++) pw[i]=pw[i-1]*2%mod;
for(int i=0;i<V;i++) f[i]=pw[c[i]]-1;
And(V,f,0);
cout<<f[0]<<endl;
}
:::
ABC295E Kth Number
:::info[题解]
设
求第
于是我们可以枚举
设
注意特判
时间复杂度
:::
:::success[代码]
const int N=2005,mod=998244353;
int n,m,k,a[N],pw[N][N],c,fac[N],infac[N],ans;
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
int C(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
void solve(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]==0) c++;
}
for(int i=0;i<N;i++){
pw[i][0]=1;
for(int j=1;j<N;j++) pw[i][j]=1ll*pw[i][j-1]*i%mod;
}
fac[0]=1,infac[0]=1;
for(int i=1;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod,infac[i]=ksm(fac[i],mod-2);
for(int v=1;v<=m;v++){
int x=0;
for(int i=1;i<=n;i++) if(a[i]!=0&&a[i]<v) x++;
int res=0;
for(int i=0;i<k-x;i++) if(c>=i) res+=1ll*C(c,i)*pw[v-1][i]%mod*pw[m-v+1][c-i]%mod,res%=mod;
ans=(ans+res)%mod;
}
cout<<1ll*ans*ksm(pw[m][c],mod-2)%mod;
}
:::
ARC139D Priority Queue 2
:::info[题解]
首先把总和拆开,将
于是考虑枚举
设初始时满足
- 若
c \ge n-x+1 ,则最终满足a_i \ge j 的i 的个数等于f(c,p)=\max(n-x+1,c+p-k) ; - 若
c \lt n-x+1 ,则最终满足a_i \ge j 的i 的个数等于f(c,p)=\min(n-x+1,c+p) 。
于是可以得到:
预处理后直接计算即可。时间复杂度
:::
:::success[代码]
#define int long long
const int N=2005,mod=998244353;
int n,m,k,x,a[N],pw[N][N],fac[N],infac[N],ans;
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%mod;
b>>=1,a=a*a%mod;
}
return res;
}
int f(int c,int p){
if(c>=n-x+1) return max(n-x+1,c+p-k);
else return min(n-x+1,c+p);
}
int C(int n,int m){
return fac[n]*infac[m]%mod*infac[n-m]%mod;
}
void solve(){
cin>>n>>m>>k>>x;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=0;i<=m;i++){
pw[i][0]=1;
for(int j=1;j<=k;j++) pw[i][j]=pw[i][j-1]*i%mod;
}
fac[0]=infac[0]=1;
for(int i=1;i<=k;i++) fac[i]=fac[i-1]*i%mod;
infac[k]=ksm(fac[k],mod-2);
for(int i=k-1;i>0;i--) infac[i]=infac[i+1]*(i+1)%mod;
for(int j=1;j<=m;j++){
int c=0;
for(int i=1;i<=n;i++) if(a[i]>=j) c++;
for(int p=0;p<=k;p++) ans+=f(c,p)*pw[m-j+1][p]%mod*pw[j-1][k-p]%mod*C(k,p),ans%=mod;
}
cout<<ans<<endl;
}
:::
ARC183C Not Argmax
:::info[题解]
首先考虑对于一个确定的排列,如何判断其是否合法。
可以参考建立笛卡尔树的过程,设当前递归到的区间为
接下来考虑如何对合法的排列进行计数。参考判断排列是否合法的过程,我们考虑计算每个区间
具体地,定义
初始化
时间复杂度
:::
:::success[代码]
const int N=505,mod=998244353;
int n,m,f[N][N],fac[N],infac[N];
bool c[N][N][N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int C(int n,int m){
return 1ll*fac[n]*infac[n-m]%mod*infac[m]%mod;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
void solve(){
cin>>n>>m,fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
for(int i=1,l,r,x;i<=m;i++) cin>>l>>r>>x,c[x][l][r]=1;
for(int l=n;l>=1;l--) for(int r=l+1;r<=n;r++) for(int x=l;x<=r;x++) c[x][l][r]=c[x][l][r]|c[x][l+1][r]|c[x][l][r-1];
for(int i=1;i<=n;i++) if(!c[i][i][i]) f[i][i]=1;
for(int i=0;i<=n;i++) f[i+1][i]=1;
for(int l=n;l>=1;l--) for(int r=l+1;r<=n;r++) for(int x=l;x<=r;x++) if(!c[x][l][r]) add(f[l][r],1ll*f[l][x-1]*f[x+1][r]%mod*C(r-l,x-l)%mod);
cout<<f[1][n]<<endl;
}
:::
ARC170C Prefix Mex Sequence
:::info[题解]
首先考虑
- 若
s_i=1 ,则a_i 只有前缀\operatorname{mex} 一种选择方案; - 若
s_i=0 ,则a_i 除了前缀\operatorname{mex} 都可以选,共有m-1 种选择方案。
直接将每个数的方案数相乘即可得到总方案数。
接着考虑
- 若
s_i=1 :- 若
a_i 的前缀\operatorname{mex} 小于等于m ,则a_i 只有前缀\operatorname{mex} 一种选择方案; - 若
a_i 的前缀\operatorname{mex} 等于m+1 ,则没有满足条件的选择方案;
- 若
- 若
s_i=0 :- 若
a_i 的前缀\operatorname{mex} 小于等于m ,则a_i 除了前缀\operatorname{mex} 都可以选,共有m-1 种选择方案; - 若
a_i 的前缀\operatorname{mex} 等于m+1 ,则所有数都可以选,共有m 种选择方案。
- 若
于是我们可以发现一个关键性质:我们只关心当前的前缀
那么我们就可以进行 dp 了。设
- 若
s_i=1 ,则f_{i,j+1}\leftarrow f_{i,j+1}+f_{i-1,j} ; - 若
s_i=0 :- 选择一个没有被选择过的,不为前缀
\operatorname{mex} 的数,f_{i,j+1} \leftarrow f_{i-1,j} \times \max(m-j,0) ; - 选择一个选择过的数,
f_{i,j} \leftarrow f_{i-1,j} \times j 。
- 选择一个没有被选择过的,不为前缀
设
时间复杂度
:::
:::success[代码]
const int N=5005,mod=998244353;
int n,m,s[N],f[N][N],ans;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>s[i];
f[0][0]=1;
for(int i=1;i<=n;i++){
if(s[i]==0){
for(int j=0;j<i;j++){
add(f[i][j+1],1ll*f[i-1][j]*max(m-j,0)%mod);
add(f[i][j],1ll*f[i-1][j]*j%mod);
}
}
else for(int j=0;j<i;j++) add(f[i][j+1],f[i-1][j]);
}
for(int j=0;j<=min(n,m+1);j++) add(ans,f[n][j]);
cout<<ans<<endl;
}
:::
ABC246Ex 01? Queries
:::info[题解]
首先考虑没有修改的时候怎么做。
定义
转移时,若
初始化
接下来我们考虑怎么处理修改操作。
我们把
其中初始时的矩阵为:
根据转移方程可知,转移时相当于给原矩阵乘上了下面的矩阵:
其中
于是我们用线段树维护每一位所对应的矩阵,每次进行单点修改,查询所有矩阵的乘积即可。
时间复杂度
:::
:::success[代码]
#define int long long
const int N=1e5+5,mod=998244353;
struct mat{
int v[4][4]={},n,m;
}a[N],val[N<<2];
mat mul(mat a,mat b){
mat res;
res.n=a.n;
res.m=b.m;
for(int i=1;i<=a.n;i++) for(int j=1;j<=b.m;j++) for(int k=1;k<=a.m;k++) res.v[i][j]+=a.v[i][k]*b.v[k][j],res.v[i][j]%=mod;
return res;
}
void modify(int x,char c){
a[x].n=a[x].m=3;
a[x].v[1][1]=a[x].v[2][2]=a[x].v[3][3]=1;
a[x].v[2][1]=a[x].v[3][1]=(c!='1');
a[x].v[1][2]=a[x].v[3][2]=(c!='0');
a[x].v[1][3]=a[x].v[2][3]=0;
}
void upd(int g){
val[g]=mul(val[g<<1],val[g<<1|1]);
}
void build(int g,int l,int r){
if(l==r){
val[g]=a[l];
return;
}
int m=(l+r)>>1;
build(g<<1,l,m);
build(g<<1|1,m+1,r);
upd(g);
}
void work(int g,int l,int r,int x){
if(l==r){
val[g]=a[l];
return;
}
if(r<x||x<l) return;
int m=(l+r)>>1;
work(g<<1,l,m,x);
work(g<<1|1,m+1,r,x);
upd(g);
}
int n,q;
string s;
void solve(){
cin>>n>>q>>s;
s=" "+s;
for(int i=1;i<=n;i++) modify(i,s[i]);
build(1,1,n);
for(int i=1;i<=q;i++){
int x;
char c;
cin>>x>>c;
modify(x,c);
work(1,1,n,x);
mat res;
res.n=1,res.m=3;
res.v[1][3]=1;
res=mul(res,val[1]);
cout<<(res.v[1][1]+res.v[1][2])%mod<<endl;
}
}
:::
P8321 『JROI-4』沈阳大街 2
:::info[题解]
考虑把
设
- 若
c_{i+1} 为1 类数:- 若
y>0 :对c_{i+1} 进行匹配,f_{i+1,j+1} \leftarrow f_{i+1,j+1}+y\times c_{i+1} \times f_{i,j} ; - 不对
c_{i+1} 进行匹配,f_{i+1,j} \leftarrow f_{i+1,j}+f_{i,j} 。
- 若
- 若
c_{i+1} 为2 类数:- 若
x>0 :对c_{i+1} 进行匹配,f_{i+1,j+1} \leftarrow f_{i+1,j+1}+x\times c_{i+1} \times f_{i,j} ; - 不对
c_{i+1} 进行匹配,f_{i+1,j} \leftarrow f_{i+1,j}+f_{i,j} 。
- 若
初始化
时间复杂度
:::
:::success[代码]
const int N=5005,mod=998244353;
int n,f[N<<1][N],s[N<<1][3];
pii p[N<<1];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i].fi,p[i].se=1;
for(int i=1;i<=n;i++) cin>>p[n+i].fi,p[n+i].se=2;
sort(p+1,p+n+n+1),reverse(p+1,p+n+n+1);
for(int i=1;i<=n+n;i++) for(int k=1;k<=2;k++) s[i][k]=s[i-1][k]+(p[i].se==k);
f[0][0]=1;
for(int i=0;i<n+n;i++){
for(int j=0;j<=min(s[i][1],s[i][2]);j++){
int x=s[i][1]-j,y=s[i][2]-j;
if(p[i+1].se==1){
if(y>0) add(f[i+1][j+1],1ll*y*p[i+1].fi%mod*f[i][j]%mod);
add(f[i+1][j],f[i][j]);
}
else{
if(x>0) add(f[i+1][j+1],1ll*x*p[i+1].fi%mod*f[i][j]%mod);
add(f[i+1][j],f[i][j]);
}
}
}
int fac=1;
for(int i=1;i<=n;i++) fac=1ll*fac*i%mod;
cout<<1ll*ksm(fac,mod-2)*f[n+n][n]%mod<<endl;
}
:::
ARC207A Affinity for Artifacts
:::info[题解]
设总成本为
考虑把
设
- 若
c_{i+1} 为1 类数:- 若
y>0 :对c_{i+1} 进行匹配,f_{i+1,j+1,k+c_{i+1}} \leftarrow f_{i+1,j+1,k+c_{i+1}}+y \times f_{i,j,k} ; - 不对
c_{i+1} 进行匹配,f_{i+1,j,k} \leftarrow f_{i+1,j,k}+f_{i,j,k} 。
- 若
- 若
c_{i+1} 为2 类数:- 若
x>0 :对c_{i+1} 进行匹配,f_{i+1,j+1,k+c_{i+1}} \leftarrow f_{i+1,j+1,k+c_{i+1}}+x \times f_{i,j,k} ; - 不对
c_{i+1} 进行匹配,f_{i+1,j,k} \leftarrow f_{i+1,j,k}+f_{i,j,k} 。
- 若
初始化
时间复杂度
:::
:::success[代码]
const int N=105,mod=998244353;
int n,x,a[N],s[N<<1][3],f[N<<1][N][N*(N-1)/2];
ll sum,v;
pii p[N<<1];
int get(int i,int j){
int a=s[i][1],b=s[i][2];
return b-a+j;
}
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>x;
for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
v=sum-x;
if(v<0) v=0;
if(v>n*(n-1)/2) v=n*(n-1)/2+1;
for(int i=1;i<=n;i++) p[i]={a[i],1},p[n+i]={i-1,2};
sort(p+1,p+n+n+1),reverse(p+1,p+n+n+1);
for(int i=1;i<=n+n;i++) for(int j=1;j<=2;j++) s[i][j]=s[i-1][j]+(p[i].se==j);
f[0][0][0]=1;
for(int i=0;i<n+n;i++){
for(int j=0;j<=min(s[i][1],s[i][2]);j++){
int x=s[i][1]-j,y=s[i][2]-j;
for(int s=0;s<=n*(n-1)/2;s++){
if(p[i+1].se==1){
if(y!=0) add(f[i+1][j+1][s+p[i+1].fi],1ll*f[i][j][s]*y%mod);
add(f[i+1][j][s],f[i][j][s]);
}
else{
if(x!=0) add(f[i+1][j+1][s+p[i+1].fi],1ll*f[i][j][s]*x%mod);
add(f[i+1][j][s],f[i][j][s]);
}
}
}
}
int ans=0;
for(int i=v;i<=n*(n-1)/2;i++) add(ans,f[n+n][n][i]);
cout<<ans<<endl;
}
:::
ARC150D Removing Gacha
:::info[题解]
显然总期望操作次数等于每个点期望操作次数之和,于是考虑对于每个点
设点
于是现在问题转化为了,有
设当前已经选择了
将所有点期望操作次数相加,得到答案为
:::
:::success[代码]
const int N=2e5+5,mod=998244353;
int n,p[N],d[N],ans,fac[N],infac[N],inv[N],pre[N];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
void solve(){
cin>>n;
for(int i=2;i<=n;i++) cin>>p[i];
fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>0;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++) inv[i]=1ll*fac[i-1]*infac[i]%mod;
for(int i=1;i<=n;i++) pre[i]=(pre[i-1]+inv[i])%mod;
for(int i=1;i<=n;i++) d[i]=d[p[i]]+1,ans=(ans+pre[d[i]])%mod;
cout<<ans<<endl;
}
:::
ABC134F Permutation Oddness
:::info[题解]
绝对值不好处理,因此考虑把
对于这种经典形式,我们可以从小到大枚举每个值
具体地,定义
-
-
-
- $x$ 和 $p_y$ 匹配:$f_{i,j,w}\leftarrow f_{i,j,w}+f_{i-1,j,w}$; - $x$ 不和 $p_y$ 匹配:$f_{i,j,w}\leftarrow f_{i,j,w}+f_{i-1,j,w} \times j \times 2$。
答案即为
时间复杂度
:::
:::success[代码]
const int N=55,K=5200,mod=1e9+7;
int n,k,f[N][N][K+K];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>k;
f[0][0][K]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
int p=2*(i-1)*(i-1);
for(int w=K-p;w<=K+p;w++){
add(f[i][j][w],1ll*f[i-1][j][w]*(2*j+1)%mod);
add(f[i][j+1][w-i*2],f[i-1][j][w]);
if(j) add(f[i][j-1][w+i*2],1ll*f[i-1][j][w]*j*j%mod);
}
}
}
cout<<f[n][0][K+k]<<endl;
}
:::
CF626F Group Projects
:::info[题解]
考虑从小到大枚举每个
设
注意到本题中
具体地,设
- 如果
a_i 作为\min ,则f_{i,j+1,k'}\leftarrow f_{i,j+1,k'}+f_{i-1,j,k} ; - 如果
a_i 作为\max ,则f_{i,j-1,k'}\leftarrow f_{i,j-1,k'}+j\times f_{i-1,j,k} ; - 如果
a_i 既作为\min 也作为\max ,则f_{i,j,k'}\leftarrow f_{i,j,k'}+f_{i-1,j,k} ; - 如果
a_i 作为中间的数,则f_{i,j,k'}\leftarrow f_{i,j,k'}+j\times f_{i-1,j,k} 。
答案即为
:::
:::success[代码]
const int N=205,K=1005,mod=1e9+7;
int n,k,a[N],f[N][N][K],ans;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1),a[0]=a[1];
f[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
for(int x=0;x<=k;x++){
int xx=x+j*(a[i]-a[i-1]);
if(xx>k) continue;
add(f[i][j+1][xx],f[i-1][j][x]);
if(j>0) add(f[i][j-1][xx],1ll*j*f[i-1][j][x]%mod);
add(f[i][j][xx],1ll*(j+1)*f[i-1][j][x]%mod);
}
}
}
for(int x=0;x<=k;x++) add(ans,f[n][0][x]);
cout<<ans<<endl;
}
:::
CF103202M United in Stormwind
:::info[题解]
设
于是对
设
计算一下
:::
:::success[代码]
const int S=1<<20;
int n,m,V,ans;
ll k,c[S],f[S];
void Or(int V,ll *f,int c){
for(int k=1;k<V;k<<=1){
for(int i=0;i<V;i++){
if(i&k) continue;
if(c==1) f[i|k]+=f[i];
else f[i|k]-=f[i];
}
}
}
void Xor(int V,ll *f,int c){
for(int k=1;k<V;k<<=1){
for(int i=0;i<V;i++){
if(i&k) continue;
ll x=f[i],y=f[i|k];
f[i]=x+y,f[i|k]=x-y;
}
}
if(c!=1) for(int i=0;i<V;i++) f[i]/=c;
}
void solve(){
cin>>n>>m>>k,V=1<<m;
for(int i=1;i<=n;i++){
string s;
int res=0;
cin>>s;
for(int j=0,p=1;j<m;j++,p<<=1) if(s[j]=='B') res+=p;
c[res]++;
}
Xor(V,c,1);
for(int i=0;i<V;i++) f[i]=c[i]*c[i];
Xor(V,f,V*2),f[0]=0,Or(V,f,1);
for(int i=1;i<V;i++) if(f[V-1]-f[V-1-i]>=k) ans++;
cout<<ans<<endl;
}
:::
ARC121E Directed Tree
:::info[题解]
将对排列
考虑容斥。设
接下来思考如何求
- 新加入一个儿子时,因为两个子树的不合法的集合是不交的,所以可以直接合并,转移方程为
f'_{u,i+j}\leftarrow f_{u,i} \times f_{v,j} ; - 加入所有儿子后,枚举点
u 是否合法,得到转移方程为f'_{u,i+1} \leftarrow f_{u,i} \times (siz_u-1-i)+f_{u,i+1} 。
于是
:::
:::success[代码]
const int N=2005,mod=998244353;
int n,p[N],siz[N],f[N][N],tmp[N],fac[N],ans;
vector <int> ve[N];
void add(int &u,int v){
u+=v;
if(u>=mod) u-=mod;
}
void dfs(int u){
f[u][0]=1;
for(auto v:ve[u]){
dfs(v);
for(int i=0;i<=siz[u];i++) tmp[i]=f[u][i],f[u][i]=0;
for(int i=0;i<=siz[u];i++) for(int j=0;j<=siz[v];j++) add(f[u][i+j],1ll*tmp[i]*f[v][j]%mod);
siz[u]+=siz[v];
}
siz[u]++;
for(int i=siz[u]-1;i>=0;i--) add(f[u][i+1],1ll*f[u][i]*(siz[u]-i-1)%mod);
}
void solve(){
cin>>n;
for(int i=2;i<=n;i++) cin>>p[i],ve[p[i]].pb(i);
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
dfs(1);
for(int i=0;i<=n;i++){
if(i&1) add(ans,mod-1ll*f[1][i]*fac[n-i]%mod);
else add(ans,1ll*f[1][i]*fac[n-i]%mod);
}
cout<<ans<<endl;
}
:::
P3349 [ZJOI2016] 小星星
:::info[题解]
考虑在树上做一个 DP:设
注意到,
这就是或卷积的形式。设
直接转移后再把每个
时间复杂度
:::
:::success[代码]
const int N=17,S=1<<17;
int n,m,V,e[N][N];
ll f[N][N][S],tmp[S],ans;
vector <int> ve[N];
void FWT(int V,ll *f,int c){
for(int k=1;k<V;k<<=1){
for(int i=0;i<V;i++){
if(i&k) continue;
if(c==1) f[i|k]+=f[i];
else f[i|k]-=f[i];
}
}
}
void dfs(int u,int fa){
for(int i=0;i<n;i++) for(int k=0;k<V;k++) if(k&(1<<i)) f[u][i][k]=1;
for(auto v:ve[u]){
if(v==fa) continue;
dfs(v,u);
for(int i=0;i<n;i++){
for(int k=0;k<V;k++) tmp[k]=0;
for(int j=0;j<n;j++){
if(!e[i][j]) continue;
for(int k=0;k<V;k++) tmp[k]+=f[u][i][k]*f[v][j][k];
}
for(int k=0;k<V;k++) f[u][i][k]=tmp[k];
}
}
}
void solve(){
cin>>n>>m,V=1<<n;
for(int i=1,u,v;i<=m;i++) cin>>u>>v,e[u-1][v-1]=1,e[v-1][u-1]=1;
for(int i=1,u,v;i<n;i++) cin>>u>>v,ve[u-1].pb(v-1),ve[v-1].pb(u-1);
dfs(0,0);
for(int i=0;i<n;i++) FWT(V,f[0][i],0),ans+=f[0][i][V-1];
cout<<ans;
}
:::
NC21207 msc 的背包
:::info[题解]
对两种物品做背包计数比较困难,因此考虑把两种物品转化为一种物品。
具体地,我们可以枚举不大于
第一部分选择买了奇数次的大小为
其中
:::
:::success[代码]
const int N=2e6+5,mod=998244353;
int n,m,k,kk,fac[N],infac[N],inv[N],ans,pro;
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
int C(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>m>>k,kk=k/2,pro=1,ans=0;
fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*(kk-i+n+m)%mod;
infac[n]=ksm(fac[n],mod-2),inv[n]=1ll*infac[n]*fac[n-1]%mod;
for(int i=n-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(kk-i-1+n+m)%mod,inv[i]=1ll*infac[i]*fac[i-1]%mod;
for(int i=1;i<=n+m;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n+m]=ksm(fac[n+m],mod-2);
for(int i=n+m-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
for(int i=kk+1;i<kk+n+m;i++) pro=1ll*pro*i%mod;
for(int i=0;i<=n;i++){
if(k%2!=i%2||i>k) continue;
int kk=(k-i)/2;
add(ans,1ll*C(n,i)*pro%mod*infac[n+m-1]%mod);
pro=1ll*pro*kk%mod*inv[i/2+1]%mod;
}
cout<<ans<<endl;
}
:::
CF2048G Kevin and Matrices
:::info[题解]
先考虑
接下来考虑
- 存在一行的最大值等于
k ,且每一行的最大值都大于等于k ; - 存在一列的最小值等于
k ,且每一列的最小值都小于等于k 。
设此时的答案为
于是问题转化为了如何求
考虑容斥:
- 钦定
i 行的最大值都小于a ; - 对于每一列:
- 先不考虑列的限制,方案数为
v^{n-i}(a-1)^i ; - 再减掉这一列的最小值大于
b 的方案数,于是需要减去(v-b)^{n-i}(\max(a-b-1,0))^i ;
- 先不考虑列的限制,方案数为
- 由于每一列是等价的,所以其对答案的贡献为:
这里我们认为
时间复杂度
:::
:::success[代码]
const int N=1e6+5,mod=998244353;
int n,m,v,fac[N],infac[N];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1;
a=1ll*a*a%mod;
}
return res;
}
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
void init(int n){
fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>0;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
}
int C(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
int f(int a,int b){
int res=0;
for(int i=0;i<=n;i++){
int val=ad(1ll*ksm(v,n-i)*ksm(a-1,i)%mod,mod-1ll*ksm(v-b,n-i)*ksm(max(a-b-1,0),i)%mod);
int ans=1ll*C(n,i)*ksm(val,m)%mod;
if(i&1) add(res,mod-ans);
else add(res,ans);
}
return res;
}
void solve(){
cin>>n>>m>>v;
init(n);
int ans=0;
for(int k=1;k<=v;k++) add(ans,(0ll+f(k,k)-f(k+1,k)-f(k,k-1)+f(k+1,k-1)+mod+mod)%mod);
cout<<ans<<endl;
}
:::
P9669 [ICPC 2022 Jinan R] DFS Order 2
:::info[题解]
设
接下来考虑怎么求
不过直接计算
:::
:::success[代码]
const int N=505,mod=998244353;
int n,fac[N],infac[N],p[N],son[N],siz[N],f[N][N],g[N][N],h[N][N];
vector <int> ve[N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
void init(int u){
siz[u]=1;
for(auto v:ve[u]) if(v!=p[u]) p[v]=u,init(v),siz[u]+=siz[v],son[u]++;
}
void dfs(int u){
memset(h,0,sizeof h);
h[0][0]=1;
for(auto v:ve[u]){
if(v==p[u]) continue;
for(int j=son[u]-1;j>=0;j--) for(int k=siz[u]-siz[v];k>=0;k--) add(h[j+1][k+siz[v]],h[j][k]);
}
for(auto v:ve[u]){
if(v==p[u]) continue;
for(int j=0;j<=son[u]-1;j++) for(int k=0;k<=siz[u]-siz[v];k++) add(h[j+1][k+siz[v]],mod-h[j][k]);
for(int k=1;k<=siz[u];k++) for(int j=0;j<son[u];j++) add(g[v][k],1ll*h[j][k-1]*fac[j]%mod*fac[son[u]-1-j]%mod);
for(int j=son[u]-1;j>=0;j--) for(int k=siz[u]-siz[v];k>=0;k--) add(h[j+1][k+siz[v]],h[j][k]);
for(int i=1;i<=n;i++) for(int j=1;j<i;j++) add(f[v][i],1ll*f[u][j]*g[v][i-j]%mod*infac[son[u]]%mod);
}
for(auto v:ve[u]) if(v!=p[u]) dfs(v);
}
void solve(){
cin>>n,fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
for(int i=1,u,v;i<n;i++) cin>>u>>v,ve[u].pb(v),ve[v].pb(u);
init(1),f[1][1]=1;
for(int u=1;u<=n;u++) f[1][1]=1ll*f[1][1]*fac[son[u]]%mod;
dfs(1);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) cout<<f[i][j]<<' ';
cout<<endl;
}
}
:::
CF2150D Attraction Theory
:::info[题解]
首先做一个经典转化:设最终位于位置
接下来考虑刻画什么样的数列
证明考虑归纳:初始时满足条件的下标区间为
- 若在位置
1\sim l-1 内设置景点,则操作后[l-1,r-1] 仍为满足条件的下标区间,位置r+1\sim n 同理; - 若在位置
l 设置景点,则操作后[l,r-1] 仍为满足条件的下标区间,位置r 同理; - 若在位置
l+1\sim r-1 内设置景点,则操作后[l+1,r-1] 仍为满足要求的下标区间。
显然,所有存在满足要求的下标区间的数列都可以被生成,因此我们只需要对所有存在满足要求的下标区间的数列
设
对于
而对于
单个
注意特殊处理
:::
:::success[代码]
const int N=2e5+5,mod=998244353;
int n,a[N],c[N],fac[N],infac[N],inv[N],s[N],ss[N],ans;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
int C(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
void solve(){
cin>>n,ans=0;
for(int i=0;i<=n+1;i++) a[i]=c[i]=fac[i]=infac[i]=inv[i]=s[i]=ss[i]=0;
fac[0]=infac[0]=inv[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2),inv[n]=1ll*infac[n]*fac[n-1]%mod;
for(int i=n-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod,inv[i]=1ll*infac[i]*fac[i-1]%mod;
for(int i=1;i<=n;i++) cin>>a[i],add(c[i],n);
for(int k=2;k<=n;k++){
for(int x=1;x<=2;x++){
for(int y=1;y<=2;y++){
int p=n+k-x-y;
if(p%2!=0||k+x+y-2>n) continue;
int v=C(p/2,k-1),w=1ll*v*(n-x-y+2)%mod*inv[k]%mod;
add(s[1],w),add(s[k+1],mod-w),add(s[n-k+2],mod-w);
if(x==2) add(ss[1],v),add(ss[n-k+2],mod-v);
if(y==2) add(ss[k],v);
}
}
}
for(int i=1;i<=n;i++) add(s[i],s[i-1]),add(ss[i],s[i]),add(ss[i],ss[i-1]),add(c[i],ss[i]),add(ans,1ll*a[i]*c[i]%mod);
cout<<ans<<endl;
}
:::
P7154 [USACO20DEC] Sleeping Cows P
:::info[题解]
为了区分「等待被匹配」和「没有被匹配」,在本文中,若一个奶牛 / 牛棚没有被匹配,则称它被扔掉了。
首先分析一下极大匹配的性质:
- 如果存在一个奶牛
i 被扔掉了,那么所有满足s_i \le t_j 的牛棚j 必须被匹配; - 如果存在一个牛棚
j 被扔掉了,那么所有满足s_i \le t_j 的奶牛i 必须被匹配。
于是考虑把所有
对于转移方程,考虑分类讨论:
- 若
a_{i+1} 为奶牛(即a_{i+1} 属于s ): -
- 若
a_{i+1} 为牛棚(即a_{i+1} 属于t ): -
答案即为
:::
:::success[代码]
const int N=3005,mod=1e9+7;
int n,m,f[N<<1][N][2];
pii p[N<<1];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n,m=n+n;
for(int i=1;i<=n;i++) cin>>p[i].fi,p[i].se=0;
for(int i=1;i<=n;i++) cin>>p[i+n].fi,p[i+n].se=1;
sort(p+1,p+1+m);
f[0][0][1]=1;
for(int i=0;i<m;i++){
for(int j=0;j<=min(n,i);j++){
if(p[i+1].se==0){
add(f[i+1][j+1][0],f[i][j][0]);
add(f[i+1][j][0],f[i][j][0]);
add(f[i+1][j+1][1],f[i][j][1]);
add(f[i+1][j][0],f[i][j][1]);
}
else{
add(f[i+1][j-1][0],1ll*j*f[i][j][0]%mod);
add(f[i+1][j-1][1],1ll*j*f[i][j][1]%mod);
add(f[i+1][j][1],f[i][j][1]);
}
}
}
cout<<(f[m][0][0]+f[m][0][1])%mod<<endl;
}
:::
P14364 [CSP-S 2025] 员工招聘
:::info[题解]
设集合
- 对于所有
i\in S ,第i 个位置的人会被录取,即要求c_i \gt \sum\limits_{j=1}^{i-1} [j \notin S] ; - 对于所有
i\in U\setminus S ,第i 个位置的人不会被录取,即要求c_i \le \sum\limits_{j=1}^{i-1} [j \notin S] 。
考虑容斥,把第一个条件的形式与第二个条件统一。具体地,钦定一个集合
- 对于所有
i \in T ,第i 个位置的人不会被录取,即要求c_i \le \sum\limits_{j=1}^{i-1} [j \notin S] ; - 对于所有
i \in S\setminus T ,第i 个位置的人没有要求。
容斥系数为
考虑从前往后 dp。设
- 若
s_{i+1}=0 ,f_{i+1,j,k} \leftarrow f_{i,j,k} ; - 若
s_{i+1}=1 :- 令
i+1 \in U\setminus S ,f_{i+1,j,k} \leftarrow f_{i+1,j,k}+f_{i,j,k} \times (a_{i-j}-k-b_i+j) ; - 令
i+1 \in S\setminus T ,f_{i+1,j+1,k} \leftarrow f_{i+1,j+1,k}+f_{i,j,k} ; - 令
i+1 \in T ,f_{i+1,j+1,k+1} \leftarrow f_{i+1,j+1,k+1}+f_{i,j,k} \times (a_{i-j}-k-b_i+j) 。
- 令
其中,系数为
答案即为
使用滚动数组优化,空间复杂度
:::
:::success[代码]
const int N=505,mod=998244353;
int n,m,c[N],s[N],a[N],b[N],f[2][N][N],fac[N],ans;
string str;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>m>>str;
for(int i=1;i<=n;i++) cin>>c[i],a[c[i]]++;
for(int i=1;i<=n;i++) s[i]=(str[i-1]=='1'),b[i]=b[i-1]+s[i];
for(int i=0;i<=n;i++) a[i]+=a[i-1];
f[0][0][0]=1,fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<n;i++){
int t=i&1;
memset(f[t^1],0,sizeof f[t^1]);
for(int j=0;j<=b[i];j++){
for(int k=0;k<=j;k++){
if(s[i+1]==0) add(f[t^1][j][k],f[t][j][k]);
else{
add(f[t^1][j+1][k],f[t][j][k]);
int x=a[i-j]-k-b[i]+j;
add(f[t^1][j][k],1ll*f[t][j][k]*x%mod);
add(f[t^1][j+1][k+1],1ll*f[t][j][k]*x%mod);
}
}
}
}
for(int j=m;j<=b[n];j++){
for(int k=0;k<=j;k++){
int x=n-b[n]+j-k;
if(k&1) add(ans,mod-1ll*f[n&1][j][k]*fac[x]%mod);
else add(ans,1ll*f[n&1][j][k]*fac[x]%mod);
}
}
cout<<ans<<endl;
}
:::
CF2129D Permutation Blackhole
:::info[题解]
由于对白色区间内的某个点染色后,这个区间会分成两个互不干扰的白色子区间,因此考虑区间 dp:设
由于对同一个数造成多次贡献会导致区间长度每次减半,所以一个数的得分上限是
设
- 若
r=n ,则w_{l,r,k}=l-1 ; - 否则,若
l=1 ,则w_{l,r,k}=r+1 ; - 否则,若
2k\le l+r ,则w_{l,r,k}=l-1 。 - 否则,
w_{l,r,k}=r+1 。
转移考虑枚举区间
- 若
s_k=-1 ,则f_{l,r,x,y}\leftarrow f_{l,r,x,y}+f_{l,k-1,x-[w_{l,r,k}=l-1],y'} \times f_{k+1,r,x',y-[w_{l,r,k}=r+1]} \times {r-l \choose k-l} ; - 若
s_k\ne-1 ,则f_{l,r,x,y}\leftarrow f_{l,r,x,y}+f_{l,k-1,x-[w_{l,r,k}=l-1],v} \times f_{k+1,r,s_k-v,y-[w_{l,r,k}=r+1]} \times {r-l \choose k-l} 。
其中,
对于所有满足
时间复杂度
:::
:::success[代码]
const int N=105,L=8,mod=998244353;
int n,s[N],fac[N],infac[N],f[N][N][L][L];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
int choose(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
int w(int l,int r,int k){
if(r==n) return l-1;
else if(l==1) return r+1;
else if(k+k<=l+r) return l-1;
else return r+1;
}
void solve(){
cin>>n;
fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
for(int i=0;i<=n+1;i++) for(int j=0;j<=n+1;j++) for(int x=0;x<L;x++) for(int y=0;y<L;y++) f[i][j][x][y]=0;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++) if(s[i]>L+L-2) return cout<<0<<endl,void();
for(int i=1;i<=n;i++) if(s[i]<=0) f[i][i][w(i,i,i)==i-1][w(i,i,i)==i+1]=1;
for(int i=0;i<=n;i++) f[i+1][i][0][0]=1;
for(int len=2;len<=n;len++){
for(int l=1,r=len;r<=n;l++,r++){
for(int x=0;x<L;x++){
for(int y=0;y<L;y++){
for(int k=l;k<=r;k++){
if(s[k]==-1){
int sum1=0,sum2=0;
for(int b=0;b<L;b++) add(sum1,f[l][k-1][x-(w(l,r,k)==l-1)][b]);
for(int a=0;a<L;a++) add(sum2,f[k+1][r][a][y-(w(l,r,k)==r+1)]);
add(f[l][r][x][y],1ll*sum1*sum2%mod*choose(r-l,k-l)%mod);
}
else{
for(int v=0;v<L;v++){
if(s[k]-v>=L||s[k]-v<0) continue;
int val1=f[l][k-1][x-(w(l,r,k)==l-1)][v],val2=f[k+1][r][s[k]-v][y-(w(l,r,k)==r+1)];
add(f[l][r][x][y],1ll*val1*val2%mod*choose(r-l,k-l)%mod);
}
}
}
}
}
}
}
cout<<f[1][n][1][0]<<endl;
}
:::
P14636 [NOIP2025] 清仓甩卖
:::info[题解]
合法的方案数并不好求,因此考虑正难则反,计算不合法的方案数。
首先尝试刻画一下不合法的条件。显然,如果小 R 购买的糖果是按照性价比排序后的一段前缀,则他买到的糖果一定是最优的;因此,我们只需要考虑中间有一些糖果因为买不起而没有买的情况。设:
- 小 R 在只剩下恰好一元钱前,购买的最后一颗满足
w_i=1 的糖果为y (其性价比为a_y ); - 小 R 在只剩下恰好一元钱后,遇到的第一颗满足
w_i=2 的糖果的编号为x (其性价比为\dfrac {a_x} 2 ); - 小 R 在只剩下恰好一元钱后,遇到的第一颗满足
w_i=1 的糖果的编号为z (其性价比为a_z );特殊地,若不存在糖果z ,则认为z=n+1 ,其原价a_z 等于0 ;
则小 R 会先购买糖果
显然,当
我们将所有糖果按照原价从大到小排序,并将此时的下标作为糖果的编号。考虑枚举
- 对于
i\in [1,x-1] ,w_i 可以等于1 或2 ; - 对于
i=x ,w_i 需要等于2 ; - 对于
i\in [x+1,y-1] ,w_i 可以等于1 或2 (若w_i=1 则它会被放在x 前面,若w_i=2 则它会被放在x 后面); - 对于
i=y ,w_i 需要等于1 ; - 对于
i\in [y+1,u] ,w_i 需要等于2 ,因为如果w_i=1 的话糖果y 就不是剩下恰好一元钱前购买的最后一颗糖果了; - 对于
i\in [u+1,v] ,w_i 需要等于2 ,因为如果w_i=1 的话糖果z 就不满足a_x \gt a_y+a_z 了; - 对于
i\in [v+1,n] ,w_i 可以等于1 或2 ,因为它们都可以作为糖果z 。
同时,设
对于
于是我们只需要枚举
时间复杂度
:::
:::success[代码]
const int N=5005,mod=998244353;
int n,m,fac[N],infac[N],pw[N],ans;
ll a[N];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int C(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
void init(){
fac[0]=infac[0]=pw[0]=1;
for(int i=1;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[N-1]=ksm(fac[N-1],mod-2);
for(int i=N-2;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
for(int i=1;i<N;i++) pw[i]=2*pw[i-1]%mod;
}
void solve(){
cin>>n>>m,ans=0,a[n+1]=0;
for(int i=1;i<=n;i++) cin>>a[i],a[i]=a[i]*2;
sort(a+1,a+n+1);
reverse(a+1,a+n+1);
for(int x=1;x<min(m,n);x++){
int y=x;
while(y<n&&a[x]/2<a[y+1]) y++;
int v=y;
while(v<n&&a[x]-a[y]<=a[v+1]) v++;
for(;a[y]<a[x];y--){
while(v<n&&a[x]-a[y]<=a[v+1]) v++;
int f=y-2,g=m-x-1;
if(f<g) continue;
add(ans,1ll*C(f,g)*pw[n-v]%mod);
}
}
cout<<(pw[n]+mod-ans)%mod<<endl;
}
:::
ARC165E Random Isolation
:::info[题解]
由于每个点最多只会被删除一次,且每个连通块不会增大,所以可以将题意转化为:随机一个排列
设
接下来考虑怎么计算
于是可以进行树形 dp:设
时间复杂度
:::
:::success[代码]
const int N=105,mod=998244353;
int n,k,siz[N],fac[N],infac[N],f[N][N][N],g[N][N],ans;
vector <int> ve[N];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1;
a=1ll*a*a%mod;
}
return res;
}
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
void dfs(int u,int fa){
f[u][1][0]=1,siz[u]=1;
for(auto v:ve[u]){
if(v==fa) continue;
dfs(v,u);
memset(g,0,sizeof g);
for(int i=0;i<=siz[u];i++) for(int j=0;i+j<=siz[u];j++) for(int p=0;p<=siz[v];p++) for(int q=0;p+q<=siz[v];q++) add(g[i+p][j+q],1ll*f[u][i][j]*f[v][p][q]%mod);
siz[u]+=siz[v];
for(int i=0;i<=siz[u];i++) for(int j=0;j<=siz[u];j++) f[u][i][j]=g[i][j];
}
f[u][0][1]=1;
}
void solve(){
cin>>n>>k;
for(int i=1,u,v;i<n;i++) cin>>u>>v,ve[u].pb(v),ve[v].pb(u);
fac[0]=infac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
infac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--) infac[i]=1ll*infac[i+1]*(i+1)%mod;
dfs(1,0);
for(int u=1;u<=n;u++){
for(int i=k+1;i<=siz[u];i++){
for(int j=0;i+j<=siz[u];j++){
int p=j+(u!=1);
add(ans,1ll*f[u][i][j]*fac[i]%mod*fac[p]%mod*infac[i+p]%mod);
}
}
}
cout<<ans<<endl;
}
:::
ARC118E Avoid Permutations
:::info[题解]
直接做有点困难,因此考虑钦定一些尚未确定的障碍点在路径上。设被钦定的障碍点的集合为
设
设
- 如果
(i,j) 处有障碍点,则不做转移; - 如果
(i,j) 处没有障碍点:- 如果
i \le n : - 不在
(i,j) 处钦定障碍点,f_{i+1,j,k,x_{i+1},b} \leftarrow f_{i+1,j,k,x_{i+1},b}+f_{i,j,k,a,b} 。 - 如果
a=0 且b=0 :在(i,j) 处钦定障碍点,f_{i+1,j,k+1,x_{i+1},1} \leftarrow f_{i+1,j,k+1,x_{i+1},1}+f_{i,j,k,a,b} 。 - 如果
j \le n : - 不在
(i,j) 处钦定障碍点,f_{i,j+1,k,a,y_{j+1}} \leftarrow f_{i,j+1,k,a,y_{j+1}}+f_{i,j,k,a,b} 。 - 如果
a=0 且b=0 :在(i,j) 处钦定障碍点,f_{i,j+1,k+1,1,y_{j+1}} \leftarrow f_{i,j+1,k+1,1,y_{j+1}}+f_{i,j,k,a,b} 。
- 如果
设
其中乘
时间复杂度
:::
:::success[代码]
const int N=205,mod=998244353;
int n,m,a[N],fac[N],f[N][N][N][2][2],ans;
bool vis[N][N],x[N],y[N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n,fac[0]=1,f[0][0][0][1][1]=1;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]!=-1) vis[i][a[i]]=1,x[i]=1,y[a[i]]=1;
else m++;
}
for(int i=1;i<=m;i++) fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=n+1;i++){
for(int j=0;j<=n+1;j++){
if(vis[i][j]) continue;
for(int k=0;k<=min(i,j);k++){
for(int a=0;a<=1;a++){
for(int b=0;b<=1;b++){
if(i<=n){
add(f[i+1][j][k][x[i+1]][b],f[i][j][k][a][b]);
if(!a&&!b) add(f[i+1][j][k+1][x[i+1]][1],f[i][j][k][a][b]);
}
if(j<=n){
add(f[i][j+1][k][a][y[j+1]],f[i][j][k][a][b]);
if(!a&&!b) add(f[i][j+1][k+1][1][y[j+1]],f[i][j][k][a][b]);
}
}
}
}
}
}
for(int k=0;k<=m;k++){
if(k&1) add(ans,mod-1ll*f[n+1][n+1][k][0][0]*fac[m-k]%mod);
else add(ans,1ll*f[n+1][n+1][k][0][0]*fac[m-k]%mod);
}
cout<<ans<<endl;
}
:::
ARC124E Pass to Next
:::info[题解]
设第
先上一个经典手法,把所有
接下来尝试计算所有
设
转移方程为:
由于这是一个环,所以我们还需要枚举一下第
解决了
其余部分都是一样的,按照上面的方法计算即可。
时间复杂度
:::
:::success[代码]
const int N=3e5+5,mod=998244353,inv2=499122177,inv6=166374059;
int n,a[N],f[N],g[N];
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
int s1(int x){
return 1ll*x*(x+1)%mod*inv2%mod;
}
int s2(int x){
return 1ll*x*(x+1)%mod*(x+x+1)%mod*inv6%mod;
}
int work(int x,int y){
if(!x) f[1]=1,g[1]=0;
else f[1]=0,g[1]=1;
for(int i=1;i<=n;i++){
f[i+1]=ad(1ll*f[i]*s1(a[i]-y)%mod,1ll*g[i]*(a[i]-y+1)%mod);
g[i+1]=ad(1ll*f[i]*ad(1ll*a[i]*s1(a[i])%mod,mod-s2(a[i]))%mod,1ll*g[i]*s1(a[i])%mod);
}
if(!x) return f[n+1];
else return g[n+1];
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cout<<ad(ad(work(0,0),work(1,0)),mod-ad(work(0,1),work(1,1)))<<endl;
}
:::
ABC213G Connectivity 2
:::info[题解]
对于图
接下来考虑怎么求
证明:显然一个环中上升的边的长度之和与下降的边的长度之和相等,即
\sum\limits_{i=1}^n [p_i \gt i](p_i-i)=\sum\limits_{i=1}^n [p_i \lt i](p_i-i) ,而左式与右式之和等于\sum\limits_{i=1}^n |p_i-i| ,因此原式成立。
于是,
性质:排列
p 是通过n 次交换操作生成的,等价于排列p 的置换环数量为偶数。证明:初始时排列
p 的置换环数量的奇偶性与n 的奇偶性相同,而每次交换操作都会使排列p 的置换环数量改变1 ,因此n 次交换操作结束后排列p 的置换环数量为偶数。
于是问题变成了,对
考虑把每一对
初始化
性质:第二维是
\mathcal O(\sqrt m) 的。证明:如果第二维的值超过了
\sqrt m ,就算每次都合并一条链,均摊下来每条链也会造成至少\sqrt m 的贡献,导致总贡献超过m 。
于是总时间复杂度为
:::
:::success[代码]
const int N=505,M=5005,S=72,mod=1e9+7;
int T,n,m,f[2][S][M][2],ans[M];
vector <pii> ve[N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>T;
for(int i=1;i<=T;i++) cin>>n>>m,ve[n].pb({m,i});
n=500,m=5000,f[0][0][0][0]=1;
for(int i=1;i<=n;i++){
int ii=i&1;
memset(f[ii],0,sizeof f[ii]);
for(int j=0;j<min(i,S);j++){
for(int k=0,kk=j;kk<=m;k++,kk++){
for(int x=0;x<2;x++){
if(!f[ii^1][j][k][x]) continue;
if(j+1<S) add(f[ii][j+1][kk][x],f[ii^1][j][k][x]);
add(f[ii][j][kk][1-x],f[ii^1][j][k][x]);
add(f[ii][j][kk][x],2ll*j*f[ii^1][j][k][x]%mod);
if(j) add(f[ii][j-1][kk][x],1ll*j*(j-1)*f[ii^1][j][k][x]%mod);
if(j) add(f[ii][j-1][kk][1-x],1ll*j*f[ii^1][j][k][x]%mod);
}
}
}
for(auto p:ve[i]) for(int k=0;k<=p.fi;k++) add(ans[p.se],f[ii][0][k][0]);
}
for(int i=1;i<=T;i++) cout<<ans[i]<<endl;
}
:::
ARC184D Erase Balls 2D
:::info[题解]
将所有球按照
设选择的球所组成的集合为
设剩余的球所组成的集合为
- 对于
u \in f(S) 且x \not\in f(S) ,设a_j \lt u \lt a_{j+1} ,则显然有Y_{a_j}\gt Y_u \gt Y_{a_{j+1}} ; - 对于
v \not\in f(S) ,设a_j \lt v \lt a_{j+1} ,则显然有Y_{a_j} \lt Y_v 或Y_v \lt Y_{a_{j+1}} 。
直接对本质不同的
显然不漏容易做到,于是问题变成了如何做到不重。考虑把
-
- 对于任意
u \not\in T ,f\big(\{u\} \cup T\big) \ne f(S) ;换句话说,即再选择任意一个球都会导致f(T) 变化。
显然满足条件的集合
于是现在只需要统计有多少个集合
- 若存在
j \lt k \lt i ,使得对于任意j \lt u \lt k 都不满足Y_i \lt Y_u \lt Y_k 且对于任意k \lt v \lt i 都不满足Y_k \lt Y_v \lt Y_j (即选择k 不会导致f(T) 变化),则不能从j 转移到i ,否则可以从j 转移到i 。
于是做一个二维前缀和即可
:::
:::success[代码]
const int N=305,mod=998244353;
int n,v[N],s[N][N],f[N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
bool check(int ax,int ay,int bx,int by){
return s[ax][ay]-s[ax][by-1]-s[bx-1][ay]+s[bx-1][by-1]==1;
}
void solve(){
cin>>n,s[1][n+2]++,s[n+2][1]++,v[1]=n+2,v[n+2]=1;
for(int i=1,x,y;i<=n;i++) cin>>x>>y,x++,y++,v[x]=y,s[x][y]++;
for(int x=1;x<=n+2;x++) for(int y=1;y<=n+2;y++) s[x][y]=s[x][y]+s[x-1][y]+s[x][y-1]-s[x-1][y-1];
f[1]=1;
for(int i=2;i<=n+2;i++){
for(int j=i-1;j>=1;j--){
if(v[j]<v[i]) continue;
bool flag=1;
for(int k=j+1;k<i;k++){
if(check(k,v[k],j,v[i])&&check(i,v[j],k,v[k])){
flag=0;
break;
}
}
if(flag) add(f[i],f[j]);
}
}
cout<<f[n+2]<<endl;
}
:::
UOJ310 黎明前的巧克力
:::info[题解]
设选出的两个集合分别为
先忽略
设
接下来考虑怎么计算
于是我们发现,
接着考虑怎么求
对
时间复杂度
:::
:::success[代码]
const int V=1<<20,N=V+5,mod=998244353;
int n,pw1[N],pw3[N],inv,f[N],h[N];
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
void FWT(int V,int *f){
for(int k=1;k<V;k<<=1){
for(int i=0;i<V;i++){
if(i&k) continue;
int x=f[i],y=f[i^k];
f[i]=x+y,f[i^k]=x-y;
}
}
}
void IFWT(int V,int *f){
for(int k=1;k<V;k<<=1){
for(int i=0;i<V;i++){
if(i&k) continue;
int x=f[i],y=f[i^k];
f[i]=ad(x,y),f[i^k]=ad(x,mod-y);
}
}
for(int i=0;i<V;i++) f[i]=1ll*f[i]*inv%mod;
}
void solve(){
cin>>n,f[0]=n;
for(int i=1,a;i<=n;i++) cin>>a,f[a]+=2;
FWT(V,f);
pw1[0]=pw3[0]=1,inv=ksm(V,mod-2);
for(int i=1;i<=n;i++) pw1[i]=-pw1[i-1],pw3[i]=3ll*pw3[i-1]%mod;
for(int i=0;i<V;i++){
int x=(3*n-f[i])/4,y=(n+f[i])/4;
h[i]=1ll*(mod+pw1[x])*pw3[y]%mod;
}
IFWT(V,h);
cout<<(h[0]+mod-1)%mod<<endl;
}
:::
P13272 [NOI2025] 序列变换
:::info[题解]
首先考虑第一问怎么做。
我们把对
显然,对于形如
- 其操作顺序一定是从左往右的;
- 操作结束后
a_l \sim a_{r-1} 的值均为0 ; - 操作结束后
a_r 的值等于[l,r] 中所有与r 奇偶性相同的i 所对应的a_i 的和减去[l,r] 中所有与r 奇偶性不同的i 所对应的a_i 的和。
形如
对于最终序列,如果
接下来考虑区间
- 若
val_{l,r}\gt 0 ,则k 需要为奇数; - 若
val_{l,r}\lt 0 ,则k 需要为偶数; - 若
val_{l,r}=0 ,则k 没有奇偶性的限制。
同时,操作过程中还不能出现负数。设以
于是可以设
答案即为
接着考虑第二问怎么做,即如何做到不重。
第一问的做法可能会出现的问题是:对于区间
于是可以设
答案即为
:::
:::success[代码]
const int N=5005,inf=1e9,mod=1e9+7;
int n,a[N],b[N],c[N],ic[N],pc[N],pic[N],l[N],r[N],tmp[N],mb[N][N][2],sic[N][N][2],g[N];
ll d[N],sb[N],f[N];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n,pc[0]=pic[0]=1;
for(int i=1;i<=n;i++) cin>>a[i],d[i]=d[i-1]+(i&1?a[i]:-a[i]);
for(int i=1;i<=n;i++) cin>>b[i],sb[i]=sb[i-1]+b[i];
for(int i=1;i<=n;i++) cin>>c[i],ic[i]=ksm(c[i],mod-2),pc[i]=1ll*pc[i-1]*c[i]%mod,pic[i]=1ll*pic[i-1]*ic[i]%mod;
for(int i=1;i<=n;i++){
l[i]=r[i]=i;
for(int j=1;j<=n;j++) tmp[j]=a[j];
while(l[i]<n&&tmp[l[i]]&&tmp[l[i]]<=tmp[l[i]+1]) tmp[l[i]+1]-=tmp[l[i]],l[i]++;
for(int j=1;j<=n;j++) tmp[j]=a[j];
while(r[i]>1&&tmp[r[i]]&&tmp[r[i]]<=tmp[r[i]-1]) tmp[r[i]-1]-=tmp[r[i]],r[i]--;
}
for(int i=1;i<=n;i++){
mb[i][i-1][0]=mb[i][i-1][1]=inf;
sic[i][i-1][0]=sic[i][i-1][1]=0;
for(int j=i;j<=n;j++){
mb[i][j][0]=mb[i][j-1][0];
mb[i][j][1]=mb[i][j-1][1];
mb[i][j][j&1]=min(mb[i][j][j&1],b[j]);
sic[i][j][0]=sic[i][j-1][0];
sic[i][j][1]=sic[i][j-1][1];
add(sic[i][j][j&1],ic[j]);
}
}
f[0]=0,g[0]=1;
for(int i=1;i<=n;i++){
f[i]=-1ll*inf*inf,g[i]=0;
for(int j=1;j<=i;j++){
if(l[j]<r[i]) continue;
ll val=d[i]-d[j-1];
int sgn=val>0,x=max(j,r[i]),y=min(i,l[j]);
if(val==0){
f[i]=max(f[i],f[j-1]+sb[i]-sb[j-1]);
add(g[i],1ll*g[j-1]*pc[i]%mod*pic[j-1]%mod);
}
else{
f[i]=max(f[i],f[j-1]+sb[i]-sb[j-1]-mb[x][y][sgn]);
add(g[i],1ll*g[j-1]*pc[i]%mod*pic[j-1]%mod*sic[x][y][sgn]%mod);
}
}
}
cout<<f[n]<<' '<<g[n]<<endl;
}
:::
AGC020E Encoding Subsets
:::info[题解]
首先考虑对于一个确定的长度为
设
对于
对于
其中,
直接计算即可得到
接下来考虑如何计算长度为
同理,设
对于
对于
其中
记忆化搜索即可。有效状态数并不多,足以通过。
:::
:::success[代码]
const int mod=998244353;
string s;
map <string,int> f,g;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
int G(string s);
int F(string s){
if(s=="") return 1;
if(f.count(s)) return f[s];
int n=s.size(),ans=0;
for(int i=1;i<=n;i++) add(ans,1ll*G(s.substr(0,i))*F(s.substr(i,n-i))%mod);
return f[s]=ans;
}
int G(string s){
if(s=="") return 1;
if(s=="0") return 1;
if(s=="1") return 2;
if(g.count(s)) return g[s];
int n=s.size(),ans=0;
for(int d=1;d<n;d++){
if(n%d!=0) continue;
string t="";
for(int i=0;i<d;i++){
bool x=1;
for(int j=i;j<n;j+=d) x&=(s[j]=='1');
t+=x+'0';
}
add(ans,F(t));
}
return g[s]=ans;
}
void solve(){
cin>>s;
cout<<F(s)<<endl;
}
:::
P10813 【MX-S2-T4】 换
:::info[题解]
显然,序列
我们可以暴力求出每个
其中,
求出
时间复杂度
:::
:::success[代码]
const int N=20,M=505,S=1<<18,mod=1e9+7;
int n,k,V,m,p[M],q[M],h[S],sum[S],f[N][S],ans;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
int lowbit(int x){
return x&-x;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
void solve(){
cin>>n>>V>>m;
for(int i=1;i<=m;i++) cin>>p[i]>>q[i],p[i]--,q[i]--;
k=1<<n;
for(int s=0;s<k;s++){
int t=s;
for(int i=1;i<=m;i++) if(((t>>p[i])&1)>((t>>q[i])&1)) t=t^(1<<p[i])^(1<<q[i]);
if(lowbit(t)+t==k) h[s]=1;
}
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int s=0;s<k;s++) sum[s]=f[i-1][s];
for(int p=1;p<k;p<<=1){
for(int s=0;s<k;s++){
if(s&p) continue;
add(sum[s|p],sum[s]);
}
}
for(int s=0;s<k;s++){
if(h[s]==0) f[i][s]=0;
else f[i][s]=ad(sum[s],mod-f[i-1][s]);
}
}
for(int w=1;w<=n;w++){
int pro=1;
for(int i=V-w+1;i<=V;i++) pro=1ll*pro*i%mod;
for(int i=1;i<=w;i++) pro=1ll*pro*ksm(i,mod-2)%mod;
add(ans,1ll*pro*f[w][k-1]%mod);
}
cout<<ans<<endl;
}
:::
ARC160F Count Sorted Arrays
:::info[题解]
考虑把排列
可以时刻维护每个
感性理解,其实只有
时间复杂度
:::
:::success[代码]
const int N=15,W=1<<15;
int n,m,V,f[N][N],g[W],h[W];
ll dp[W],ans=1;
bool get(int s,int x,int y){
return ((s>>x)&1)>((s>>y)&1);
}
void solve(){
cin>>n>>m,V=1<<n;
for(int i=0;i<n;i++) for(int j=0;j<n;j++) f[i][j]=1;
for(int s=0;s<V;s++) g[s]=s;
for(int i=0;i<=n;i++) h[((1<<i)-1)<<(n-i)]=1;
for(int c=1;c<=m;c++){
int x,y;
cin>>x>>y;
x=(x+ans)%n,y=(y+ans+ans)%n;
if(x>y) swap(x,y);
if(!f[x][y]){
cout<<ans<<endl;
continue;
}
for(int i=0;i<n;i++) f[x][i]=f[i][x]=f[y][i]=f[i][y]=0;
for(int s=0;s<V;s++){
if(get(g[s],x,y)) g[s]^=(1<<x)^(1<<y);
for(int i=0;i<n;i++){
if(get(g[s],min(i,x),max(i,x))) f[x][i]=f[i][x]=1;
if(get(g[s],min(i,y),max(i,y))) f[y][i]=f[i][y]=1;
}
}
for(int s=0;s<V;s++) dp[s]=0;
dp[0]=1;
for(int s=1;s<V;s++){
if(!h[g[s]]) continue;
for(int i=0;i<n;i++) if(s&(1<<i)) dp[s]+=dp[s^(1<<i)];
}
ans=dp[V-1];
cout<<ans<<endl;
}
}
:::
CF1603E A Perfect Problem
:::info[题解]
显然,一个序列是否是完美序列与序列中每个元素的顺序无关,因此不妨考虑对一个单调不降的序列
性质
1 :单调不降的序列a 是完美序列,当且仅当其每个前缀都是好序列。证明:对于序列
a 的子序列a_{p_1},\dots,a_{p_m} ,只要区间a_{p_1}\sim a_{p_m} 是好的,由于a_{p_1}a_{p_m}\ge\sum\limits_{i=p_1}^{p_m} a_i \ge \sum\limits_{i=1}^m a_{p_i} ,子序列a_{p_1},\dots,a_{p_m} 就是好的;而对于序列a 的区间a_l\sim a_r ,只要前缀a_1\sim a_r 是好的,由于a_la_r \ge a_1a_r \ge \sum\limits_{i=1}^r a_i \ge \sum\limits_{i=l}^r a_i ,区间a_l\sim a_r 就是好的。
于是,判定一个序列是否是完美序列,只需要检查其排序后每个前缀是否是好序列即可。
不过计算一个单调不降的完美序列能对应多少个未排序的完美序列,还需要知道每个值在序列中的出现次数;因此我们可以从小到大枚举每个值
具体地,设
直接计算的时间复杂度是
考虑对
设
同时,因为
性质
2 :m \ge n-2\sqrt n 。证明:因为对于任意不大于
n 的正整数k 都满足a_k \ge k ,所以m \ge \sum\limits_{i=1}^n (a_i-m) \ge \sum\limits_{i=1}^{n-m} (n-i+2-m) \ge \dfrac{(n-m+1)(n-m)}{2} ,即2m \ge (n-m)^2+(n-m) ,显然当m \lt n-2\sqrt n 时该式不成立。
于是,根据性质
注意特判一些边界情况,例如值为
:::
:::success[代码]
const int N=205,M=20;
int n,mod,f[N][N][N],C[N][N],ans;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>n>>mod;
for(int i=0;i<=n+1;i++) C[i][0]=1;
for(int i=1;i<=n+1;i++) for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
for(int m=max(1,n-M);m<=n+1;m++){
memset(f,0,sizeof f);
f[m-1][0][0]=1;
for(int i=m;i<=n+1;i++) for(int j=(i>m);j<i;j++) for(int s=0;s<=m;s++){
if(!f[i-1][j][s]) continue;
for(int k=0,jj=j,ss=s;jj<=i&&ss<=m;k++,jj++,ss+=i-m) if(m*(i-jj)>=ss) add(f[i][jj][ss],1ll*f[i-1][j][s]*C[n-j][k]%mod);
}
for(int s=0;s<=m;s++) add(ans,f[n+1][n][s]);
}
cout<<ans<<endl;
}
:::
AGC008E Next or Nextnext
:::info[题解]
设从
显然
- 若环上的每一个点都满足
a_i=p_i ,则G_2 中有一个一模一样的环; - 若环上的每一个点都满足
a_i=p_{p_i} 且环长为奇数,则G_2 中有一个环长相同但不一样的环; - 若环上的每一个点都满足
a_i=p_{p_i} 且环长为偶数,则G_2 中有两个环长为该环长度一半的环; - 若环上既存在满足
a_i=p_i 的点又存在满足a_i=p_{p_i} 的点,则G_2 中会存在一棵由一个环和若干条链组成的基环内向树。
接下来考虑
先考虑
- 若
j \ne 0 :- 从
cnt_i 个环中选出2j 个环,方案数为cnt_i \choose 2j ; - 从
2j 个环中选出左侧的j 个环,钦定左侧的环的顺序为1\sim j ,枚举右侧环的顺序,并去掉交换同一行两个环的方案,得到方案数为\frac{{2j \choose j}j!}{2^j} ; - 每对环有
i 种合并方式,方案数为i^j ;
- 从
- 若
i 为奇数且i\ne1 ,则每个不被合并的环也有2 种方案,方案数为2^{cnt_i-2j} ;
将每个步骤的方案数相乘即可得到这个部分的结果,将每个
再考虑
- 若
e>l_i ,则插回的方案数为2 ; - 若
e=l_i ,则插回的方案数为1 ; - 若
e<l_i ,则插回的方案数为0 。
将每条链插回的方案数相乘即可得到这部分的结果,答案即为每个环和每棵基环内向树的方案数的乘积。
朴素实现的时间复杂度为
:::
:::success[代码]
const int N=1e5+5,mod=1e9+7;
int n,a[N],c[N],vis[N],ans=1,fac[N<<1],infac[N<<1];
vector <int> ve[N];
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
int ad(int a,int b){
a+=b;
if(a>=mod) a-=mod;
return a;
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%mod;
b>>=1,a=1ll*a*a%mod;
}
return res;
}
int C(int n,int m){
return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;
}
void dfs(int u,bool f,int len){
f|=(ve[u].size()!=1);
vis[u]=1,len++;
if(vis[a[u]]){
if(!f) return c[len]++,void();
vector <int> s,p,l;
s.pb(u),vis[u]=2;
int v=a[u];
while(v!=u) s.pb(v),vis[v]=2,v=a[v];
int m=s.size();
for(int i=0;i<m;i++){
int u=s[i];
if(ve[u].size()==2){
if(vis[ve[u][0]]==2) v=ve[u][1];
else v=ve[u][0];
int cnt=0;
while(1){
vis[v]=1,cnt++;
if(ve[v].size()==2) cout<<0<<endl,exit(0);
if(ve[v].size()==0) break;
v=ve[v][0];
}
p.pb(i),l.pb(cnt);
}
}
int k=p.size();
for(int i=0;i<k;i++){
int e;
if(i==0) e=p[i]+m-p[k-1];
else e=p[i]-p[i-1];
if(e>l[i]) add(ans,ans);
if(e<l[i]) cout<<0<<endl,exit(0);
}
return;
}
dfs(a[u],f,len);
}
void solve(){
cin>>n;
fac[0]=infac[0]=1;
for(int i=1;i<=n+n;i++) fac[i]=1ll*i*fac[i-1]%mod;
infac[n+n]=ksm(fac[n+n],mod-2);
for(int i=n+n-1;i>0;i--) infac[i]=1ll*(i+1)*infac[i+1]%mod;
for(int i=1;i<=n;i++) cin>>a[i],ve[a[i]].pb(i);
for(int i=1;i<=n;i++) if(ve[i].size()>2) cout<<0<<endl,exit(0);
for(int i=1;i<=n;i++){
if(vis[i]) continue;
dfs(i,0,0);
}
for(int i=1;i<=n;i++){
int sum=0;
for(int j=0;j+j<=c[i];j++){
int res=1;
if(j!=0) res=1ll*C(c[i],j+j)*C(j+j,j)%mod*fac[j]%mod*ksm((mod+1)/2,j)%mod*ksm(i,j)%mod;
if((i&1)&&i!=1) res=1ll*res*ksm(2,c[i]-j-j)%mod;
add(sum,res);
}
ans=1ll*ans*sum%mod;
}
cout<<ans<<endl;
}
:::
AGC027E ABBreviate
:::info[题解]
注意到,如果我们把
首先考虑字符串
只要
接下来考虑字符串
-
-
- 设
|t|=m ,则s 可以被分成m+1 段子字符串e_1,e_2,\dots,e_{m+1} ,其中前i 段均满足f(e_i)=f(t_i) ,第m+1 段满足f(e_{m+1})=0 (e_{m+1} 可以为空串)。
同理,只要
可以在特判
初始化
由于每个字符串
:::
:::success[代码]
const int N=1e5+5,mod=1e9+7;
int n,a[N],nxt[N][3],suf[N],dp[N],ans;
bool flag;
string s;
void add(int &a,int b){
a+=b;
if(a>=mod) a-=mod;
}
void solve(){
cin>>s,n=s.size();
for(int i=1;i<=n;i++) a[i]=s[i-1]=='a'?1:2,flag|=a[i]==a[i-1];
if(!flag) return cout<<1<<endl,void();
nxt[n+1][1]=nxt[n+1][2]=n+2,dp[1]=1;
for(int i=n;i>=1;i--){
suf[i]=(suf[i+1]+a[i])%3;
if(a[i]==1) nxt[i][1]=i+1,nxt[i][2]=nxt[i+1][1];
if(a[i]==2) nxt[i][1]=nxt[i+1][2],nxt[i][2]=i+1;
}
for(int i=1;i<=n;i++) add(dp[nxt[i][1]],dp[i]),add(dp[nxt[i][2]],dp[i]);
for(int i=2;i<=n+1;i++) if(suf[i]==0) add(ans,dp[i]);
cout<<ans<<endl;
}
:::
AGC020F Arcs on a Circle
:::info[题解]
将所有线段从短到长排序,考虑断环为链,以第
虽然每条线段的左端点都是在实数范围内随机的,但我们其实只关心小数部分的相对大小关系,且可以认为小数部分两两不同,因此我们可以考虑直接枚举
考虑 dp:设
直接计算即可。时间复杂度
:::
:::success[代码]
const int N=7,C=51;
int n,c,V,a[N],p[N],rk[N];
ll ans,dp[N*C][N*C][1<<N],cnt;
void solve(){
cin>>n>>c,V=1<<(n-1);
for(int i=1;i<=n;i++) cin>>a[i],p[i]=i;
sort(a+1,a+n+1);
while(1){
memset(dp,0,sizeof dp);
dp[0][a[n]*n][0]=1,cnt++;
for(int i=1;i<n;i++) rk[p[i]]=i;
for(int i=0;i<n*c;i++){
for(int j=i;j<=n*c;j++){
for(int s=0;s<V;s++){
int k=rk[i%n];
dp[i+1][j][s]+=dp[i][j][s];
if(k!=0&&(!(s&(1<<(k-1))))) dp[i+1][min(max(j,i+a[k]*n),c*n)][s|(1<<(k-1))]+=dp[i][j][s];
}
}
}
ans+=dp[n*c][n*c][V-1];
if(!next_permutation(p+1,p+n)) break;
}
printf("%.18lf",ans*1.0/cnt/pow(c,n-1));
}
:::
P12558 [UOI 2024] Heroes and Monsters
:::info[题解]
我们把
更具体地,集合
- 设在
S 中的第i 小的元素为a_x ,则有a_x \gt b_i ; - 设不在
S 中的第i 小的元素为a_y ,则有a_y \lt b_{m+i} 。
于是,当