【集训】组合计数与构造专题
组合计数:
CF1824B2
为奇数时,注意到每次好点移动一格至少会增加 的长度,所以好点个数为 。 为偶数时,注意到好点一定在一条链上,我们计算出有多少条边 满足 和 为好点,答案就是边数
可以得到,必须两侧的子树大小为 时,才能满足
具体的,左侧子树大小为 ,右侧子树大小为 ,则方案数为
由于算的是期望,答案需除以总方案数,即 。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
const int N=200005,mod=1e9+7;
ll fac[N],ifac[N];
int h[N],e[N*2],ne[N*2],idx;
int n,k,s[N];
ll ans;
void add(int u,int v){
e[++idx]=v,ne[idx]=h[u],h[u]=idx;
}
ll poww(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
ll C(ll n,ll m){
if(n<0||m<0||n<m) return 0;
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
void init(int n){
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
ifac[n]=poww(fac[n],mod-2);
for(int i=n;i;i--) ifac[i-1]=ifac[i]*i%mod;
}
void dfs(int u,int f){
s[u]=1;
for(int i=h[u];i;i=ne[i]){
int j=e[i];
if(j==f) continue;
dfs(j,u);
s[u]+=s[j];
ans=(ans+C(s[j],k>>1)*C(n-s[j],k>>1)%mod)%mod;
}
}
signed main(){
cin>>n>>k;
if(k&1) return cout<<1<<endl,0;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
add(u,v),add(v,u);
}
init(n),dfs(1,0);
cout<<(ans*ifac[n]%mod*fac[k]%mod*fac[n-k]%mod+1)%mod;
return 0;
}
ARC163D
考虑分成两个集合
设
转移则考虑第
考虑将
-
在
内加入最大点 , 向 内的连边都是逆向的,向 内的连边任意。钦定 条为正向的,则系数为 。 -
在
内加入最大点 , 向 内的连边都是正向的,向 内的连边任意。钦定 条为正向的,则系数为 。
时间复杂度
#include <bits/stdc++.h>
using namespace std;
const int N=31,M=N*N>>1,mod=998244353;
int n,m,C[M][M],f[N][N][M],ans;
int main(){
cin>>n>>m;
C[0][0]=1;
for(int i=1;i<=n*(n-1)/2;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
f[0][0][0]=1;
for(int i=0;i<n;i++){
for(int j=0,s=i;s<n;j++,s++){
for(int k=0;k<=m;k++){
for(int x=0;x<=i;x++)
f[i+1][j][k+x]=(f[i+1][j][k+x]+1ll*C[i][x]*f[i][j][k])%mod;
for(int x=0;x<=j;x++)
f[i][j+1][k+i+x]=(f[i][j+1][k+i+x]+1ll*C[j][x]*f[i][j][k])%mod;
}
}
}
int ans=mod-C[n*(n-1)/2][m];
for(int i=0,j=n;i<=n;i++,j--) ans=(ans+f[i][j][m])%mod;
cout<<ans<<endl;
return 0;
}
ARC148E
满足以下性质:
- 不能出现两个
的数相邻; - 如果
,那么 。
考虑按照
维护一个可用位置数 s,然后:
- 遇到
,出现次数为 y,那我们有 种方案插入,并且可用位置数减去 - 遇到
,出现次数为 y,这个插入的方案数相当于,把这 个数划分成 段,允许有空段,把这 段依次插入。这个用插板法可得方案数为 ,并且可用位置数加上 。
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define PII pair<ll,ll>
#define fi first
#define se second
const int N=200005,mod=998244353;
ll poww(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
ll n,m,a[N],fac[N],ifac[N],idx;
PII b[N];
ll C(ll n,ll m){
if(n<0||m<0||n<m) return 0;
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
void init(ll n){
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
ifac[n]=poww(fac[n],mod-2);
for(int i=n;i>=1;i--) ifac[i-1]=ifac[i]*i%mod;
}
bool cmp(PII a,PII b){
return abs(a.fi*2-m)>abs(b.fi*2-m)||(abs(a.fi*2-m)==abs(b.fi*2-m)&&a.fi*2>m);
}
signed main(){
cin>>n>>m;
map<ll,ll> mp;
for(int i=1;i<=n;i++)
cin>>a[i],mp[a[i]]++;
for(PII p : mp)
b[++idx]=p;
init(n);
sort(b+1,b+idx+1,cmp);
ll ans=1,s=1;
for(int i=1;i<=idx;i++){
if(b[i].fi*2<m){
ans=ans*C(s,b[i].se)%mod;
s-=b[i].se;
}
else{
ans=ans*C(s+b[i].se-1,s-1)%mod;
s+=b[i].se;
}
}
cout<<ans<<endl;
return 0;
}
P8292 [省选联考 2022] 卡牌
考虑容斥,钦定
自然不能
另一方面,如果给出的所有质数都
对于一般的情况,我们预处理
这个直接暴力
查询时,枚举给出的
每次枚举所有大质数是不行的,不过我们可以提前预处理
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2005,mod=998244353;
int n,m,p2[1000005],cnt[N];
int popcnt[1<<14],f[1<<14][305],g[1<<14];
int p[20005],pri[N],vis[N],idx,id[N],ps[N],pb[N];
void init(){
for(int i=2;i<=2000;i++){
if(!vis[i]) pri[++idx]=i;
for(int j=1;j<=idx&&i*pri[j]<=2000;j++){
vis[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
for(int i=1;i<=idx;i++) id[pri[i]]=i;
}
signed main(){
init(); // 预处理质数
cin>>n;
p2[0]=1;
for(int i=1;i<=n;i++) p2[i]=p2[i-1]*2%mod; // 2 的幂
for(int i=1;i<=n;i++){
int tmp;
cin>>tmp;
cnt[tmp]++;
}
//ps[i] 表示 i 所有的小质数的状态,pb[i] 表示 i 的大质数
for(int i=1;i<=2000;i++){
int x=i;
for(int j=1;j<=13;j++){
if(x%pri[j]==0) ps[i]|=1<<(j-1);
while(x%pri[j]==0) x/=pri[j];
}
pb[i]=x;
}
pb[43*43]=43;
//popcnt[i] i 的二进制表示的 1 的个数
for(int s=0;s<1<<13;s++){
popcnt[s]=popcnt[s>>1]+(s&1);
for(int i=1;i<=2000;i++)
if((ps[i]&s)==0) f[s][id[pb[i]]]+=cnt[i],g[s]+=cnt[i];
}
cin>>m;
vector<int> v;
while(m--){
int c,now=0;cin>>c;
v.clear();
for(int i=1;i<=c;i++){
cin>>p[i];
if(p[i]<=41) now|=1<<(id[p[i]]-1);
else v.push_back(id[p[i]]);
}
int ans=0;
for(int s=now,pre=1;pre;pre=s,s=(s-1)&now){
int val=1,cnt=g[s];
for(int x:v){
int y=f[s][x];
cnt-=y;
val=val*(p2[y]-1)%mod;
}
val=val*p2[cnt]%mod;
if(popcnt[s]&1) val=mod-val;
ans=(ans+val)%mod;
}
cout<<ans<<endl;
}
return 0;
}
ARC157D
如果一共有奇数个
否则假设一共有
记
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2005,mod=998244353;
int n,m,tot,ans;
int s1[N],s2[N],g[N],f1[N],f2[N],p1[N],p2[N],sum[N][N];
char s[N][N];
int S(int x1,int y1,int x2,int y2){
return sum[x2][y2]-sum[x1][y2]-sum[x2][y1]+sum[x1][y1];
}
int cnt1[N],cnt2[N];
void solve(int k1){
int k2=tot*2/k1,c1=0,c2=0;
for(int i=1;i<=n;i++) if(s1[i]!=s1[i-1]&&s1[i]%k1==0) p1[++c1]=i;
for(int i=1;i<=m;i++) if(s2[i]!=s2[i-1]&&s2[i]%k2==0) p2[++c2]=i;
if(c1<<1!=k2||c2<<1!=k1) return;
for(int i=1;i<=c1;i++)
for(int j=1;j<=c2;j++)
if(S(p1[i-1],p2[j-1],p1[i],p2[j])!=2) return;
for(int i=1;i<=c1;i++) cnt1[i]=0;
for(int i=1;i<=c2;i++) cnt2[i]=0;
for(int i=1;i<=n;i++) if(s1[i]%k1==0) cnt1[s1[i]/k1]++;
for(int i=1;i<=m;i++) if(s2[i]%k2==0) cnt2[s2[i]/k2]++;
int res=1;
for(int i=1;i<c1;i++) res=res*cnt1[i]%mod;
for(int i=1;i<c2;i++) res=res*cnt2[i]%mod;
ans=(ans+res)%mod;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i]+1;
for(int j=1;j<=m;j++){
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
if(s[i][j]=='Y') s1[i]++,s2[j]++,sum[i][j]++;
}
}
for(int i=1;i<=n;i++) s1[i]+=s1[i-1];
for(int i=1;i<=m;i++) s2[i]+=s2[i-1];
tot=s1[n];
if(tot&1) return cout<<0,0;
for(int i=2;i<=n*m;i+=2) if(tot%i==0) solve(i);
cout<<ans<<endl;
return 0;
}
P3447 [POI2006] KRY-Crystals
发现只要有某个
考虑枚举第一次出现某个
#include<bits/stdc++.h>
#define ull unsigned long long
const ull mod=1ull<<64;
void add(ull &x,ull v){ x+=v; if(x>=mod) x-=mod;}
using namespace std;
const int N=55;
ull f[N][2][2],lim[N],n,X;
signed main(){
cin>>n,X=0;
ull ans=0,now=0;
for(int i=1;i<=n;i++) cin>>lim[i],lim[i]++,now^=lim[i];
for(int w=31;w>=0;w--){
bool flag=1;
for(int i=31;i>w;i--)
if((now^X)&(1<<i)){
flag=0; break;
}
if(!flag) break;
int U=(1<<w)-1;
memset(f,0,sizeof(f));
f[0][0][0]=1;
for(int i=0;i<n;i++)
for(int c=0;c<2;c++)
for(int x=0;x<2;x++)
if(f[i][c][x]){
if(lim[i+1]&(1<<w)){
add(f[i+1][c^1][x],1ll*f[i][c][x]*((lim[i+1]&U)));
add(f[i+1][c][1],1ll*f[i][c][x]*(x?(U+1):1ll));
}
else add(f[i+1][c][x],1ll*f[i][c][x]*((lim[i+1]&U)));
}
if(X&(1<<w)) add(ans,f[n][1][1]);
else add(ans,f[n][0][1]);
}
cout<<ans-1<<endl;
return 0;
}
构造:
CF1450C2
发现操作次数至多
将所有格子分成三类,第
不难发现只要一类格子全是
那么有三种构造方案:
-
把第
类格子上的 全改为 ,第 类格子上的 全改为 。 -
把第
类格子上的 全改为 ,第 类格子上的 全改为 。 -
把第
类格子上的 全改为 ,第 类格子上的 全改为 。
显然这三种都能使局面变成平局,且操作总数为
#include <bits/stdc++.h>
using namespace std;
const int N=305;
int T,n,k,cnt1,cnt2,cnt3;
int uid[N][N];
char mp[N][N],ans1[N][N],ans2[N][N],ans3[N][N];
void print(char s[N][N]){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) cout<<s[i][j];
cout<<endl;
}
}
void init(){
memset(mp,'0',sizeof(mp)),memset(ans1,'0',sizeof(ans1)),memset(ans2,'0',sizeof(ans2)),memset(ans3,'0',sizeof(ans3)),
k=cnt1=cnt2=cnt3=0;
}
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
char ch=getchar();
while(ch!='O'&&ch!='X'&&ch!='.') ch=getchar();
if(ch!='.') k++;
mp[i][j]=ch;
uid[i][j]=(i+j)%3;
}
}
memcpy(ans1,mp,sizeof(mp)),memcpy(ans2,mp,sizeof(mp)),memcpy(ans3,mp,sizeof(mp));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(uid[i][j]==0 && mp[i][j]=='O') ans1[i][j]='X',cnt1++;
if(uid[i][j]==1 && mp[i][j]=='X') ans1[i][j]='O',cnt1++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(uid[i][j]==1 && mp[i][j]=='O') ans2[i][j]='X',cnt2++;
if(uid[i][j]==2 && mp[i][j]=='X') ans2[i][j]='O',cnt2++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(uid[i][j]==2 && mp[i][j]=='O') ans3[i][j]='X',cnt3++;
if(uid[i][j]==0 && mp[i][j]=='X') ans3[i][j]='O',cnt3++;
}
if(cnt1<=k/3) print(ans1);
else if(cnt2<=k/3) print(ans2);
else if(cnt3<=k/3) print(ans3);
init();
}
return 0;
}
CF618F
CF1270G
由于
建立一张
这张图中每个点的出度都为
可以发现对于任意一个环,环上的点对应的下标就是我们要找的答案。
证明:
记
一旦
即
将等式右边展开得:
显而易见,
证毕!
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],vis[N];
vector<int>ans;
inline int rd()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL),cout.tie(NULL);
int T=rd();
while(T--){
n=rd();
for(int i=1;i<=n;i++){
a[i]=rd();
a[i]=i-a[i];
vis[i]=0;
}
int x=1;
while(!vis[x]){
vis[x]=1;
x=a[x];
}
ans.push_back(x); x=a[x];
while(x!=ans[0]){
ans.push_back(x);
x=a[x];
}
cout<<ans.size()<<endl;
while(ans.size()){
cout<<ans.back()<<" ";
ans.pop_back();
}
cout<<endl;
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!