组合数学专项训练记录 2024.3
[abc221_e]LEQ
依题意得,当确定了两个端点后,中间的可选可不选,考虑枚举左端点,找比它大的右端点,求方案数,时间复杂度
考虑优化,若两个端点分别是i,j,则方案数为
代码:
#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9,mod=998244353;
int n,a[300005];
int ls[5000005],rs[5000005],idx,rt;
ll val[5000005];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int add(int id,int l,int r,int x,int v)
{
if(!id) id=++idx;
if(l==r)
{
val[id]=(qpow(2,v)+val[id])%mod;
return id;
}
int mid=(l+r)>>1;
if(x<=mid) ls[id]=add(ls[id],l,mid,x,v);
else rs[id]=add(rs[id],mid+1,r,x,v);
val[id]=(val[ls[id]]+val[rs[id]])%mod;
return id;
}
ll query_val(int id,int l,int r,int ql,int qr)
{
if(!id||l>qr||r<ql) return 0;
if(l>=ql&&r<=qr)
{
return val[id];
}
int mid=(l+r)>>1;
ll res=0;
if(mid>=ql) res=query_val(ls[id],l,mid,ql,qr);
if(mid<qr) res=(res+query_val(rs[id],mid+1,r,ql,qr))%mod;
return res%mod;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
ll ans=0;
for(int i=n;i>0;i--)
{
ll cnt=query_val(rt,1,p,a[i],p);
// printf("%d ",cnt);
ans=(ans+cnt*qpow(qpow(2,i+1),mod-2)%mod)%mod;
rt=add(rt,1,p,a[i],i);
}
printf("%lld",ans%mod);
return 0;
}
[abc243_f]Lottery
设
代码:
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=998244353;
int n,m,K,sum,a[55];
ll dp[55][55][55],fac[55],inv[55];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll C(int n,int m)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
fac[0]=inv[0]=1;
for(int i=1;i<=K;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[K]=qpow(fac[K],p-2);
for(int i=K-1;i>0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
{
dp[i][0][0]=dp[i-1][0][0];
for(int j=1;j<=K;j++)
{
for(int k=1;k<=min(i,m);k++)
{
dp[i][j][k]=dp[i-1][j][k];
for(int l=1;l<=j;l++)
{
dp[i][j][k]=(dp[i][j][k]+dp[i-1][j-l][k-1]*C(j,l)%p*qpow(a[i],l)%p*qpow(qpow(sum,l),p-2)%p)%p;
}
}
}
}
printf("%lld",dp[n][K][m]);
return 0;
}
Claris的剑
可以发现,我们可以先构造一个序列:
二元组的个数为
所以可以枚举n,m,答案为:
考虑优化,可以枚举
代码:
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int p=1e9+7;
ll fac[2000005],inv[2000005];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll C(int m,int n)
{
return fac[n]*inv[m]%p*inv[n-m];
}
int n,m;
int main()
{
scanf("%d%d",&n,&m);
fac[0]=inv[0]=1;
for(int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[n]=qpow(fac[n],p-2);
for(int i=n-1;i>0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
ll ans=1;
for(int i=0;i<=min(n,m)-2;i++)
{
ans=(ans+C(i+1,(n+i-2)/2+1))%p;
// printf("%d %d %d\n",i+1,(n+i-2)/2+1,C(i+1,(n+i-2)/2+1));
}
// printf("%lld\n",ans);
for(int i=0;i<=min(n-3,m-2);i++)
{
ans=(ans+C(i+1,(n+i-3)/2+1))%p;
}
printf("%lld",ans);
return 0;
}
Array
很明显,当数组中的数确定下来后,顺序也一定确定了,所以考虑隔板发求每个元素取的个数,每个元素可取多个或不取,答案就是
代码:
#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int n;
ll fac[2000005];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
int main()
{
scanf("%d",&n);
fac[0]=1;
for(int i=1;i<=2*n;i++)
{
fac[i]=fac[i-1]*i%p;
}
ll ans=fac[2*n-1]*qpow(fac[n-1],p-2)%p*qpow(fac[n],p-2)%p;
printf("%lld",(ans*2-n+p)%p);
return 0;
}
Lucky Subsequence
可以发现,非幸运数字随便取,所以考虑如何求幸运数字的方案数
记第i种幸运数字的个数为
记得滚动数组优化
代码:
#include<cstdio>
#include<algorithm>
#include<map>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,k,a[100005],idx,sum,m;
ll dp[100005],cnt[100005],fac[100005],inv[100005];
map<int,int>mp;
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll C(int n,int m)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
scanf("%d%d",&n,&k);
fac[0]=inv[0]=1;
for(int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[n]=qpow(fac[n],p-2);
for(int i=n-1;i>0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
int x=a[i],fl=0;
while(x)
{
int r=x%10;
if(r!=4&&r!=7)
{
fl=1;
break;
}
x/=10;
}
if(!fl)
{
if(!mp[a[i]])
{
mp[a[i]]=++m;
}
cnt[mp[a[i]]]++;
}
else sum++;
}
dp[0]=1;
for(int i=1;i<=m;i++)
{
for(int j=m;j>0;j--)
{
dp[j]=(dp[j]+dp[j-1]*cnt[i]%p)%p;
}
}
ll ans=0;
for(int i=0;i<=min(k,sum);i++)
{
ans=(ans+C(sum,i)*dp[k-i]%p)%p;
// printf("%d %d %d\n",i,C(n-idx,i),dp[k-i]);
}
printf("%lld",ans);
return 0;
}
White, Black and White Again
很明显,当每一天发生哪种事,发生多少件定好后,方案数就是
所以可以枚举i,j,表示i~j天发生坏事,然后隔板法解决即可
代码:
#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+9;
int n,w,b;
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll fac[4005],inv[4005],ans;
ll C(int n,int m)
{
return fac[n]*inv[n-m]%p*inv[m]%p;
}
int main()
{
scanf("%d%d%d",&n,&w,&b);
fac[0]=1;
for(int i=1;i<=4000;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[4000]=qpow(fac[4000],p-2);
for(int i=3999;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
//printf("1");
for(int i=2;i<n;i++)
{
for(int j=i;j<n;j++)
{
int l1=j-i+1;
int l2=n-l1;
if(l1>b||l2>w) continue;
//printf("%d %d\n",l1,l2);
ans=(ans+C(b-1,l1-1)*fac[b]%p*C(w-1,l2-1)%p*fac[w]%p)%p;
// printf("%d %d %d %d",C(l1-1,b-1),fac[b],C(l2-1,w-1),fac[w]);
}
}
printf("%lld",ans);
return 0;
}
Vasily the Bear and Beautiful Strings
可以发现,第一个1以后的1都没有用,因为连续的两个数中只要出现1必然会变成0
所以考虑枚举可取的第一个1的位置,用组合数求解即可
n=0,m=0,m=1要特判!!!
代码:
#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll fac[200005],inv[200005],ans;
ll C(int n,int m)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
int n,m,g;
int main()
{
scanf("%d%d%d",&n,&m,&g);
if(n==0)
{
if(m==1) printf("%d",g);
else printf("%d",g^1);
return 0;
}
if(m==0)
{
if(n%2) printf("%d",g^1);
else printf("%d",g);
return 0;
}
fac[0]=1;
for(int i=1;i<=n+m;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[m+n]=qpow(fac[n+m],p-2);
for(int i=m+n-1;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
for(int i=0;i<=n;i++)
{
if(i%2!=g) continue;
ans=(ans+C(n+m-i-1,m-1))%p;
}
if(m==1)
{
if(n%2==g) ans=(ans-1+p)%p;
else ans=(ans+1)%p;
}
printf("%lld",ans);
return 0;
}
Increase Sequence
某次考试的题,可是依然不会
设区间数为cnt
- 若
则无解 - 若
则需要开一段区间,cnt++ - 若
则需要闭合一段区间,方案数cnt - 若
则分2种情况,一种是不操作,一种是先闭合一段区间,再新开一段,方案数cnt+1
代码:
#include<cstdio>
#define ll long long
using namespace std;
const int p=1e9+7;
int a[2002],n,h,c[2005];
ll ans,cnt;
int main()
{
scanf("%d%d",&n,&h);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=h-a[i];
}
for(int i=1;i<=n+1;i++)
{
c[i]=a[i]-a[i-1];
}
ans=1;
for(int i=1;i<=n+1;i++)
{
// if(a[i]>h) ans=0;
if(c[i]>1||c[i]<-1) ans=0;
if(c[i]==-1)
{
ans=ans*cnt%p;
cnt--;
}
if(c[i]==1)
{
cnt++;
}
if(c[i]==0)
{
ans=ans*(cnt+1)%p;
}
}
//if(cnt) ans=0;
printf("%lld",ans);
return 0;
}
Gerald and Giant Chess
很明显,总移动方式为
若
代码:
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5,p=1e9+7;
int h,w,n;
ll fac[N+5],inv[N+5],f[2005];
struct node{
int x,y;
}a[2005];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
bool cmp(node x,node y)
{
if(x.x==y.x)return x.y<y.y;
return x.x<y.x;
}
ll C(int m,int n)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
scanf("%d%d%d",&h,&w,&n);
fac[0]=1;
for(int i=1;i<=N;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[N]=qpow(fac[N],p-2);
for(int i=N-1;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
}
sort(a+1,a+n+1,cmp);
n++;
a[n].x=h,a[n].y=w;
for(int i=1;i<=n;i++)
{
f[i]=C(a[i].x-1,a[i].x+a[i].y-2);
for(int j=1;j<i;j++)
{
if(a[i].y>=a[j].y)
{
f[i]=(f[i]-f[j]*C(a[i].x-a[j].x,a[i].x+a[i].y-a[j].x-a[j].y)%p+p)%p;
}
}
}
printf("%lld",f[n]);
return 0;
}
Pluses everywhere
考虑dp,设
考虑单独计算每一个
所以考虑枚举j
有一个特例,
总方案数为:
代码:
#include<cstdio>
#include<string>
#include<iostream>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,k;
string s;
ll fac[100005],inv[100005],pre[100005];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll C(int n,int m)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
scanf("%d%d",&n,&k);
cin>>s;
fac[0]=1;
s=" "+s;
for(int i=1;i<=n;i++)
{
fac[i]=fac[i-1]*i%p;
pre[i]=(pre[i-1]+s[i]-'0')%p;
}
inv[n]=qpow(fac[n],p-2);
for(int i=n-1;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
ll q=1,ans=0;
for(int i=1;i<=n-k;i++)
{
ans=(ans+q*(pre[n-i]*C(n-1-i,k-1)%p+(s[n-i+1]-'0')*C(n-i,k)%p)%p)%p;
q=q*10%p;
}
printf("%lld",ans);
return 0;
}
Special Matrices
设
滚动数组优化即可
代码:
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#define ll long long
using namespace std;
int n,m,p,l[505];
ll dp[2][505][505];
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++) l[i]=2;
for(int i=1;i<=m;i++)
{
string s;
cin>>s;
s=" "+s;
int tmp=0;
for(int j=1;j<=n;j++)
{
if(s[j]=='1')
{
l[j]--;
tmp++;
}
}
if(tmp>2)
{
printf("0");
return 0;
}
}
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++)
{
if(l[i]<0)
{
printf("0");
return 0;
}
if(l[i]==1) cnt1++;
if(l[i]==2) cnt2++;
// printf("%d ",l[i]);
}
// printf("\n");
int now=0,lst=1;
dp[0][cnt1][cnt2]=1;
for(int i=m+1;i<=n;i++)
{
now=now^1,lst=lst^1;
memset(dp[now],0,sizeof(dp[now]));
for(int j=0;j<=n;j++)
{
for(int k=0;k<=n;k++)
{
if(dp[lst][j][k]==0) continue;
// printf("%d %d %d\n",i,j,k);
if(k>=1&&j>=1) dp[now][j][k-1]=(dp[now][j][k-1]+dp[lst][j][k]*j*k%p)%p;
if(k>=2) dp[now][j+2][k-2]=(dp[now][j+2][k-2]+dp[lst][j][k]*k*(k-1)/2)%p;
if(j>=2) dp[now][j-2][k]=(dp[now][j-2][k]+dp[lst][j][k]*j*(j-1)/2)%p;
}
}
}
printf("%lld",dp[now][0][0]);
return 0;
}
hdu5396Expression
区间dp,设
若符号为*,则
若符号为+,则
若符号为-,则
代码:
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,a[105];
string s;
ll dp[105][105],fac[105],inv[105];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll C(int n,int m)
{
return fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
fac[0]=1;
for(int i=1;i<=100;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[100]=qpow(fac[100],p-2);
for(int i=99;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
while(cin>>n)
{
memset(dp,0,sizeof(dp));
// printf("%d %d\n",fac[0],inv[0]);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i][i]=a[i];
// printf("%d %d\n",fac[i],inv[i]);
}
cin>>s;
s=" "+s;
for(int l=2;l<=n;l++)
{
for(int i=1;i+l-1<=n;i++)
{
int j=i+l-1;
for(int k=i;k<j;k++)
{
ll tmp=0;
if(s[k]=='*')
{
tmp=(dp[i][k]*dp[k+1][j])%p;
}
if(s[k]=='+')
{
tmp=(dp[i][k]*fac[j-k-1]%p+dp[k+1][j]*fac[k-i]%p)%p;
}
if(s[k]=='-')
{
tmp=(dp[i][k]*fac[j-k-1]%p-dp[k+1][j]*fac[k-i]%p)%p;
}
dp[i][j]=(dp[i][j]+tmp*C(j-i-1,k-i)%p)%p;
// printf("%lld ",C(j-i-1,k-i));
}
}
}
printf("%lld\n",(dp[1][n]+p)%p);
}
return 0;
}
hdu4661 Message Passing
可以发现,先把消息传给同一个人,再由他传给所有人效率最高
因为这是一颗树,一个图与其反图的拓扑序数量是一样的,考虑将信息都传给根,设
但是对于每个根都跑一遍dfs,显然会T,考虑up and down
设t为fa去掉u子树后的树,则:
当u为根时,合并u子树与t,则:
联立得:
代码:
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int p=1e9+7,N=1e6;
int T,n,head[N+5],edgenum;
struct edge{
int to,nxt;
}e[2000005];
ll fac[N+5],inv[N+5];
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int siz[N+5];
ll dp[N+5],dp2[N+5],ans;
void init()
{
edgenum=ans=0;
memset(head,0,sizeof(head));
}
void dfs(int u,int fa)
{
siz[u]=dp[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
dp[u]=dp[u]*dp[v]%p*inv[siz[v]]%p;
}
dp[u]=dp[u]*fac[siz[u]-1]%p;
}
void dfs2(int u,int fa)
{
if(u!=1)
{
dp2[u]=dp2[fa]*siz[u]%p*qpow(n-siz[u],p-2)%p;
}
ans=(ans+dp2[u]*dp2[u]%p)%p;
// printf("%d %lld %lld\n",u,dp2[u],dp[u]);
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs2(v,u);
}
}
int main()
{
scanf("%d",&T);
fac[0]=1;
for(int i=1;i<=N;i++)
{
fac[i]=fac[i-1]*i%p;
}
inv[N]=qpow(fac[N],p-2);
for(int i=N-1;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1)%p;
}
while(T--)
{
scanf("%d",&n);
init();
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
dp2[1]=dp[1];
dfs2(1,0);
printf("%lld\n",ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效