组合计数学习笔记1

P1450 [HAOI2008]硬币购物

多组问询。每次 (c_i) 相同,给定 (d_i,s),求满足 (0le x_ile d_i)(sum_{i=1}^{4} x_ic_i=S)(x) 的方案数。

考虑容斥。我们假设 (f_i) 表示不受限制时,和为 (i) 的方案数。那么我们容斥哪些物品一定超额使用。

[ans=sum_{ssubset {1,2,3,4}} (-1)^{|s|} f_{(S-sum_{iin s}c_i(d_i+1))} ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=1e5+9;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int Q,n=4,ss=15,c[5],d[5],S,f[N];

bool get(int s,int i) {return s&(1<<i-1);}

signed main() {
	rep(i,1,n) c[i]=read();
	Q=read();
	f[0]=1;
	rep(i,1,n) rep(j,c[i],100000) f[j]+=f[j-c[i]];
	while(Q--) {
		rep(i,1,n) d[i]=read();
		S=read();
		int ans=0;
		rep(s,0,ss) {
			int popc=0,w=0,res=0,tS=S;
			rep(i,1,n) if(get(s,i)) {
				popc++;
				tS-=c[i]*(d[i]+1);
			}
			res=(tS<0?0:f[tS]), w=(popc%2?-1:1);
			ans+=res*w;
		}
		printf("%lld
",ans);
	}
	return 0;
}

P3158 [CQOI2011]放棋子

(n imes m) 的棋盘,有 (c) 种不同颜色的棋子,每种颜色的棋子有 (a_i) 个。棋子不能叠放,且每行每列的所有棋子颜色必须相同。问方案数。

考虑 DP。(f(i,j,k)) 表示对于前 (i) 种颜色的棋子,放了 (j imes k) 大小的的棋盘格。转移觉得要枚举第 (i) 类棋子占了多少格子,设其占了 (x)(y) 列。贡献很难算。所以我们要求 (g(i,j,k)) 表示 (i) 枚同色棋子占了 (j)(k) 列(也可以理解成 (j imes k) 的棋盘格)。我们按行考虑。我们转移时枚举第 (j+1) 行放置了多少枚棋子,且有多少枚棋子原本就在 (j imes k) 的格子中。我们设前者为 (x),后者为 (y),则有

[g(i+x,j+1,k+x-y) leftarrow inom{k}{y} inom{k+x-y}{k} g(i,j,k) ]

于是我们 (f) 就有转移

[f(i+1,j+x,k+y) leftarrow inom{j+x}{x} inom{k+y}{y} f(i,j,k) g(a_{i+1},x,y) ]

最后我们就可以计算答案

[ans=sum_j sum_k inom{n}{j} inom{m}{k} f_{c,j,k} ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=69,mod=1e9+9;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int fac[N], inv[N], ifac[N];
void pre(int mn=30) {
    inv[0]=inv[1]=fac[0]=ifac[0]=1;
    for(int i=1;i<=mn;i++) fac[i]=fac[i-1]*i%mod;
    for(int i=2;i<=mn;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    for(int i=1;i<=mn;i++) ifac[i]=ifac[i-1]*inv[i]%mod;
}
int C(int x,int y) {
    if(x<0||y<0||x<y) return 0;
    else return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}
void add(int &x,int y) {(x+=y)%=mod;}

int n,m,c,a[N],g[909][N][N],f[N][N][N];

signed main() {
	n=read(), m=read(), c=read();
	int mx=0;
	rep(i,1,c) a[i]=read(), mx=max(mx,a[i]);
	pre();
	g[0][0][0]=1;
	rep(i,0,mx) rep(j,0,n-1) rep(k,0,m) if(g[i][j][k]) {
		rep(x,1,m) rep(y,0,min(x,k)) {
			if(k+x-y>m) continue;
			add(g[i+x][j+1][k+x-y],C(k,y)*C(k+x-y,k)%mod*g[i][j][k]%mod);
		}
	}
	f[0][0][0]=1;
	rep(i,0,c-1) rep(j,0,n) rep(k,0,m) if(f[i][j][k]) {
		rep(x,1,n-j) rep(y,1,m-k) {
			add(f[i+1][j+x][k+y],C(j+x,x)*C(k+y,y)%mod*f[i][j][k]%mod*g[a[i+1]][x][y]%mod);
		}
	}
	int ans=0;
	rep(j,1,n) rep(k,1,m) add(ans,C(n,j)*C(m,k)%mod*f[c][j][k]%mod);
	printf("%lld
",ans);
	return 0;
}

后记:你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!你不要写错模数啊喂!

P3349 [ZJOI2016]小星星

暴力状压的话由于枚举子集所以 (O(3^nn^3))。DP 为 (f(u,i,S)) 表示 (u) 子树内,树点 (u) 对应图点 (i),图点使用状态为 (S)

考虑容斥。我们要求做到树点和图点一一对应,即最终所有的图点都被使用了。我们用容斥优化的时候考虑丢掉状压的 (S) 维度,容斥哪些点一定不使用,设其状态为 (S)。于是我们有

[f_S(u,i)=prod_{vin son_u} sum_{(i,j)in E_G} f_{S}(v,j)\ ans=sum_{Ssubset V_G} (-1)^{|S|} sum_{i otin S} f_{S}(1,i) ]

复杂度 (O(2^nn^3))

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=19;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int n,m,f[N][N];
vector<int>G[N],T[N];

bool get(int s,int i) {return s&(1<<i-1);}
void dfs(int u,int fa,int s) {
	for(auto v:T[u]) if(v!=fa) dfs(v,u,s);
	rep(i,1,n) f[u][i]=!get(s,i);
	rep(i,1,n) if(!get(s,i)) {
		for(auto v:T[u]) if(v!=fa) {
			int res=0;
			for(auto j:G[i]) if(!get(s,j)) res+=f[v][j];
			f[u][i]*=res;
		}
	}
}

signed main() {
	n=read(), m=read();
	rep(i,1,m) {
		int u=read(), v=read();
		G[u].push_back(v), G[v].push_back(u);
	}
	rep(i,2,n) {
		int u=read(), v=read();
		T[u].push_back(v), T[v].push_back(u);
	}
	int S=(1<<n)-1,ans=0;
	rep(s,0,S) {
		int w=1,res=0;
		dfs(1,0,s);
		rep(i,1,n) {
			if(get(s,i)) w=-w;
			else res+=f[1][i];
		}
		ans+=w*res;
	}
	printf("%lld
",ans);
	return 0;
}

P3214 [HNOI2011]卡农

卡农好听!!这出题人怕不是不会乐理!!

(n) 个不同元素,一个片段是一个包含任意多个元素的不可重非空集合。问有多少种由 (m) 片段组成的的不可重集合,使得集合中每个元素出现次数为偶数。

神仙题。

Trick1:有序集合更加方便,于是我们将无序集转化为有序集,最终除以 (m!) 就行了。
Trick2:补集转换。

同样是 DP。(f(i)) 表示 (i) 个片段组成的合法集合数。首先考虑总数。我们发现如果任意确定了 (i-1) 个不可重片段,那么第 (i) 个片段也就随之确定了。这解决了偶数的问题。所以(满足偶数条件以及前 (i-1) 不可重的条件)总数为 (A_{2^n-1}^{i-1})

由两个可能不满足的条件,片段不同以及片段非空。考虑存在片段相同的情况,由于前 (i-1) 不可重,那么一定是第 (i) 个和之前的有重复,由于减去两个相同的片段后总数还是偶数,并且我们重复的片段有 (2^n-1-(i-2)) 种,所以这部分答案为 ((i-1)(2^n-i+1)f(i-2));考虑片段非空的情况,由于前 (i-1) 非空,那么一定是第 (i) 个是空片段,所以这部分答案为 (f(i-1))

[f(0)=1\ f(1)=0\ f(i)=A_{2^n-1}^{i-1}-f(i-1)-(i-1)(2^n-i+1)f(i-2) ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=1e6+9,mod=1e8+7;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int n,m,a[N],f[N];

int ksm(int x,int y) {
	if(y==0) return 1;
	else return ksm(x*x%mod,y/2)*(y%2?x:1)%mod;
}

signed main() {
	n=read(), m=read();
	int s=ksm(2,n);
	a[0]=1, f[0]=1, f[1]=0;
	rep(i,1,m) a[i]=a[i-1]*(s-i)%mod;
	rep(i,1,m) f[i]=(a[i-1]-f[i-1]-(i-1)*(s-i+1)%mod*f[i-2]%mod)%mod;
	f[m]=(f[m]+mod)%mod;
	rep(i,1,m) f[m]=f[m]*ksm(i,mod-2)%mod;
	printf("%lld
",f[m]);
	return 0;
}

P6672 [清华集训2016] 你的生命已如风中残烛

(m) 张不同的牌,其中有 (n) 张牌上有正整数 (w)(满足 (sum w=m)),另有 (m-n) 上有 0。问有多少种排列方法,使得 (forall iin[1,m] sum w_{j=1}^{i}ge i)

神仙题。

Trick1 如果每个 (w) 都减去 (1),那么我们只需要保证前缀和非负。这样每个 (i) 的约束限制就一样了。

然后就是 Raney 定理。参考具体数学中的处理方法。因为不能保证每次1都在前面,于是我们数列取反然后再前面加上1,此时有 (m-n+1)(1)

于是 (ans=frac{m!}{m-n+1})

https://www.luogu.com.cn/blog/forever-captain/solution-p6672

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int mod=998244353;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

signed main() {
    int n=read(),m=0,ans=1;
    rep(i,1,n) m+=read();
    rep(i,2,m) ans=ans*(i==m-n+1?1:i)%mod;
    printf("%lld
",ans);
    return 0;
}

P2606 [ZJOI2010]排列计数

很水的一道题。可以转化为一棵完全二叉树的满足小根堆性质的标号排列数量。考虑树形 DP (f(u)) 表示 (u) 子树内的答案。转移时可以首先确定 (u) 一定是最小的 (1),然后我们把剩下的几个随便分配给左右子树,然后让左右子树来安排这些标号的排列。

[f(u)=inom{sz_l+sz_r}{sz_l} f(l) f(r) ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=2e6+9;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int n,mod,sz[N],f[N],fac[N],inv[N],ifac[N];

void pre() {
    inv[0]=inv[1]=fac[0]=ifac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    for(int i=2;i<=n;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    for(int i=1;i<=n;i++) ifac[i]=ifac[i-1]*inv[i]%mod;
}
int C(int x,int y) {
    if(x<0||y<0||x<y) return 0;
    else return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

void dfs(int u) {
	if(u>n) return f[u]=1,void();
	dfs(u*2),dfs(u*2+1);
	sz[u]=sz[u*2]+sz[u*2+1]+1;
	f[u]=C(sz[u*2]+sz[u*2+1],sz[u*2])*f[u*2]%mod*f[u*2+1]%mod;
}

signed main() {
	n=read(), mod=read();
	pre();
	dfs(1);
	printf("%lld
",f[1]);
	return 0;
} 

P3244 [HNOI2015]落忆枫音

给定一个 DAG,问添加了一条单向边 ((x,y)) 后,有多少叶向生成树。

考虑不管加入的边,答案危

[prod_{i=2}^n deg^{in}_i ]

每个点都选择一个父亲,乘起来。

然后我们加了一条边。现在我们连成的可能形成了环。于是我们不能要这种情况。我们考虑补集转换。对于一个环 (S),满足选择了 (S) 的方案数为 (prod u otin S deg^{in}_u),即环上的点指定父亲为环上的点,其他点随意选择。

所以答案是

[ans=prod_{u=2}^n deg^{in}_u - sum_S frac{prod_{u=1}^n deg^{in}_u}{prod_{uin S} deg^{in}_u} ]

[f_u=sum_{sin {u o x}} frac{prod deg(i)}{prod deg_{s_i}} ]

则有答案为 (-dp_x+prod deg(i)),初始值有 (dp_y=frac{prod deg(i)}{deg_y})

转移有

[f_u=frac{1}{deg_u}sum_{v o u} f_v ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=200009,mod=1e9+7;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int ksm(int x,int y) {
	if(y==0) return 1;
	else return ksm(x*x%mod,y/2)*(y%2?x:1)%mod; 
}

int n,m,x,y,deg[N],f[N],prod=1,ans=1;
vector<int>fr[N];
bool vst[N];

void dfs(int u) {
	if(vst[u]) return; vst[u]=1;
	if(u==y) {f[u]=prod*ksm(deg[u],mod-2); return;}
	for(auto v:fr[u]) dfs(v);
	for(auto v:fr[u]) f[u]=(f[u]+f[v])%mod;
	f[u]=f[u]*ksm(deg[u],mod-2)%mod;
}

signed main() {
	n=read(), m=read(), x=read(), y=read();
	rep(i,1,m) {
		int u=read(), v=read();
		fr[v].push_back(u), deg[v]++;
	}
	deg[1]++;
	rep(i,1,n) {
		prod=(prod*deg[i])%mod,
		ans=(ans*(deg[i]+(i==y)))%mod;
	}
	dfs(x);
	printf("%lld
",(ans-f[x]+mod)%mod);
	return 0;
}

P3643 [APIO2016]划艇

求有多少个数列 (c_N) ,对于每个非零位置 (c_i) 满足

  • (c_i=0)
  • (a_ile c_ile b_i)
  • (c_i>c_{上一个非零位置})

sub1:(a_i=b_i),直接 check 是否满足 (a_i>a_{i-1}),如果是那么1个,否则0个
sub2:(sum_{b_i-a_i}le 10^6),考虑 DP (f(i,j)) 表示前 (i) 个数且第 (i) 个数为 (a_i+j)。用vector存DP数组。应该不简单。

现在考虑优化这个DP。可以考虑离散化。我们把所有的 ([a_i,b_i]) 区间离散化成 (O(n)) 个端点,形成 (O(n)) 个区间。(f(i,j)) 表示前 (i) 个数,第 (i) 个数非零且在第 (j) 个区间内。我们 DP 的时候枚举上一个区间,以及出现在上一个区间的最后一个数是哪个。于是我们现在要计算在第 (j) 个区间内,有 (m) 个数且第 (i) 个数不为 0 的方案数。显然我们能得到,设有 (m) 数满足条件,则其贡献为 (inom{len+m-1}{m})

所以有 DP 方程

[f(i,j)=sum_{k=1}^{j-1} sum_{p=0}^{i-1} inom{len_j-m+1}{m} f_{p,k} ]

由于这个 (k) 和组合数没有关系,考虑把 k 的求和以到组合数后面去,然后就可以前缀和优化

[f(i,j)=sum_{p=0}^{i-1}inom{len_j+m-1}{m} s_{p,j-1} ]

组合数不能直接贺板子,很不好,还需要吸收恒等式硬搞。

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=1009,mod=1e9+7;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int n,inv[N],l[N],r[N],p[N],li[N],ri[N],len[N],cnt,c[N],f[N][N],s[N];

void pre() {
    inv[0]=inv[1]=1;
    rep(i,2,1000) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}

struct node {int id,val,lr;} b[N];
bool cmp(const node &x,const node &y) {return x.val<y.val;}
void discrete() {
	rep(i,1,n)
		b[i*2-1]=(node){i,l[i],0}, b[i*2]=(node){i,r[i],1};
	sort(b+1,b+2*n+1,cmp);
	rep(i,1,2*n) {
		if(b[i].val!=b[i-1].val) cnt++;
		if(b[i].lr) ri[b[i].id]=cnt;
		else li[b[i].id]=cnt;
		p[cnt]=b[i].val;
	} 
}

signed main() {
	n=read();
	pre();
	rep(i,1,n) l[i]=read(), r[i]=read(), r[i]++;
	discrete();
	rep(i,1,cnt-1) len[i]=p[i+1]-p[i];
	s[0]=1;
	rep(j,1,cnt-1) {
		c[0]=1;
		rep(i,1,n) c[i]=c[i-1]*(len[j]+i-1)%mod*inv[i]%mod;
		per(i,n,1) if(li[i]<=j&&j<ri[i]) {
			int m=1,cur=len[j];
			per(p,i-1,0) {
				f[i][j]=(f[i][j]+cur*s[p]%mod)%mod;
				if(li[p]<=j&&j+1<=ri[p]) cur=c[++m];
			}
			s[i]=(s[i]+f[i][j])%mod;
		}
	}
	int ans=0;
	rep(i,1,n) ans=(ans+s[i])%mod;
	printf("%lld
",ans);
	return 0;
}

P3228 [HNOI2013]数列

对于20%数据,可以DP (f(i,j)) 表示前 (i) 天且第 (i) 天为 (j) 的方案数。卡卡常应该能过去。

正解要一个trick考虑差分序列的情况。显然知道差分序列和 (a_1) 就可以唯一确定整个序列。一共有 (m^{k-1}) 种差分序列/假如确定了差分序列 (d),则 (a_1)

[f(d)=n-sum_i d_i ]

种可能性

于是题目中求得即

[ans=sum_d f(d)\ =sum_d (n-sum_{i=1}^{k-1} d_i)\ = m^{k-1}n-sum_{i=1}^{k-1}sum_{d} d_i\ ]

(c_i)(i) 在所有可能的差分序列中所出现的次数,有

[ans=m^{k-1}n-sum_{i=1}^{m} i c_i ]

由于每一个数分布概率均匀,所以 (c_i) 都是相等的。所以设出现次数为 (c),有

[c=m^{k-1} imes frac{k-1}{m}\ ans=m^{k-1}n-csum_{i=1}^{m} i\ = m^{k-1}n-m^{k-2} imes (k-1) imes frac{m(m+1)}{2} ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=x*10+c-48,c=getchar();}
    return x*f;
}

int n,k,m,p;

int ksm(int x,int y) {
	if(y==0) return 1;
	else return ksm(x*x%p,y/2)*(y%2?x:1)%p;
}

signed main() {
	n=read(), k=read(), m=read(), p=read();
	n%=p;
	int c=ksm(m,k-2)*(k-1)%p;
	int ans=ksm(m,k-1)*n%p;
	ans-=c*((m+1)*m/2%p)%p;
	printf("%lld
",(ans+p)%p);
	return 0;
}

P4071 [SDOI2016]排列计数

连我都能一眼看出答案的sb题。设错排数 (f_n),则有

[ans=inom{n}{m} f_{n-m} ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=1e6+9,mod=1e9+7;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int f[N],fac[N], inv[N], ifac[N];
void pre(int n) {
    inv[0]=inv[1]=fac[0]=ifac[0]=f[0]=f[2]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    for(int i=2;i<=n;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    for(int i=1;i<=n;i++) ifac[i]=ifac[i-1]*inv[i]%mod;
    for(int i=3;i<=n;i++) f[i]=(i-1)*(f[i-1]+f[i-2])%mod;
}
int C(int x,int y) {
    if(x<0||y<0||x<y) return 0;
    else return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

signed main() {
	int T=read();
	pre(1000000);
	while(T--) {
		int n=read(), m=read();
		printf("%lld
",C(n,m)*f[n-m]%mod);
	}
	return 0;
}

P1350 车的放置

还是连我都能做出来的sbt。

首先考虑 ((a+c) imes d) 的长方形,易得答案

[ans=k!inom{a+c}{k} inom{d}{k} ]

然后考虑假如这个上方的长方形,我们枚举其中有几个车,易得答案

[ans=sum_{i=0}^{k} i! inom{a}{i} inom{b}{i} (k-i)!inom{a+c-i}{k-i} inom{d}{k-i} ]

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int N=2009,mod=1e5+3;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int a,b,c,d,k,fac[N],inv[N],ifac[N],ans;
void pre(int n) {
    inv[0]=inv[1]=fac[0]=ifac[0]=1;
    rep(i,1,n) fac[i]=fac[i-1]*i%mod;
    rep(i,2,n) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    rep(i,1,n) ifac[i]=ifac[i-1]*inv[i]%mod;
}
int C(int x,int y) {
    if(x<0||y<0||x<y) return 0;
    else return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

signed main() {
	a=read(), b=read(), c=read(), d=read(), k=read();
	pre(2000);
	rep(i,0,k) {
		int res=C(a,i)*C(b,i)%mod*C(a+c-i,k-i)%mod*C(d,k-i)%mod*fac[i]%mod*fac[k-i]%mod;
		ans=(ans+res)%mod;
	}
	printf("%lld
",ans);
	return 0;
}

P3166 [CQOI2014]数三角形

还是小清新组合数题。

我们考虑补集转换。随便选3个点-横着共线-竖着共线-斜着共线,其中斜着要枚举向量 (vec{AC}=(x,y))

我们为了方便让 (n:=n+1, m:=m+1),即 (n,m) 分别代表格点数

随便选3个点:(inom{nm}{3}=frac{nm(nm-1)(nm-2)}{6})

横着共线:(ninom{m}{3}=frac{nm(m-1)(m-2)}{6})

竖着共线:(minom{n}{3}=frac{mn(n-1)(n-2)}{6})

斜着共线:考虑到以整点为端点的向量 ((x,y)) 所经过整点数(不包括两端)为 (gcd (x,y)-1),所以我们计算点 A 的取法数量和点 B 的取法数量

[2sum_{x=1}^{n} sum_{y=1}^{m} (n-x) (m-y) (gcd(x,y)-1) ]

OEIS 上好像有 (n=1)(n=2) 的。

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define per(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;

inline int read() {
    register int x=0, f=1; register char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+c-48,c=getchar();}
    return x*f;
}

int gcd(int a,int b) {
	if(b==0) return a;
	else return gcd(b,a%b);
}

signed main() {
	int n=read()+1, m=read()+1;
	int a=n*m*(n*m-1)/2*(n*m-2)/3, b=n*m*(m-1)/2*(m-2)/3, c=m*n*(n-1)/2*(n-2)/3, d=0;
	rep(x,1,n) rep(y,1,m) d+=2*(n-x)*(m-y)*(gcd(x,y)-1);
	printf("%lld
",a-b-c-d);
	return 0;
}
原文地址:https://www.cnblogs.com/TetrisCandy/p/14332876.html