容斥
容斥
Devu and Flowers
题意:
共 \(n\) 种花,每种 \(f_i\) 朵。求选择 \(s\) 朵方案。答案对 \(1e9+7\)取模。\(1\leq n\leq 20,0\leq f_i\leq 10^{12},0\leq s\leq 10^{14}\)
思路:插板法+容斥。因为可能选 \(0\) 朵,所以不能直接用插板法,我们可以整体加\(n\),表示每个都先选了1朵,这样再来求。
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+7;
ll n,a[22],m,ans;
ll inv[22];
ll ksm(ll x,ll p){
ll as=1;
while(p){
if(p&1){
as=(as*x)%mod;
}
x=(x*x)%mod;
p>>=1;
}
return as;
}
ll C(ll x,ll y){
ll s=1,fm=1;
if(x>y||x<0||y<0) return 0;
if(y==0||x==0) return 1;
y%=mod;
y+=mod;
for(ll i=y;i>=y-x+1;i--){
s=(s*i)%mod;
}
// cout<<s<<endl;
for(ll i=1;i<=x;i++){
fm=(fm*inv[i])%mod;
}
// fm=ksm(fm,mod-2);
s=(s*fm)%mod;
return s;
}
void init(){
for(ll i=1;i<=20;i++){
inv[i]=ksm(i,mod-2);
}
}
int main(){
// cout<<C(1,5);
init();
scanf("%lld%lld",&n,&m);
ans=C(n-1,n+m-1);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<(1<<n);i++){
int fl=0;
ll sum=(m+n-1);
for(int j=1;j<=n;j++){
if((i>>(j-1))&1){
sum=sum-(a[j]+1);
fl^=1;
}
}
// cout<<ans<<endl;
sum=sum%mod;
if(fl==0) ans=(ans+C(n-1,sum))%mod;
else ans=((ans-C(n-1,sum))%mod+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}
启示:
- 求组合数时若 \(n\) 小 \(m\) 大,可以直接枚举来计算。
- 若要求可以不选时的插板法,可以把整体都加 \(n\),表示每个选了一个,再继续操作。
CF348D Turtles
题意:\(n\times m\) 网格图,图中有些格子上有障碍物,两只乌龟从 \((1,1)\) 走到 \((n,m)\),可以向下或向右,求两只乌龟不交路径对数,答案对 \(1e9+7\) 取模。\(n,m\leq3000\)。
思路:一只乌龟的很简单,设 \(f_{i,j}\) 表示走到 \((i,j)\) 的路径数,那么 \(f_{i,j}=[a_{i,j}=="."](f_{i,j}+f_{i-1,j}+f_{i,j-1})\)。
怎么判断不交呢?我们可以先看有哪些是一定相交的。首先,两只乌龟一定分别经过 \((1,2)\) 和 \((n-1,m)\),\((2,1)\) 和 \((n,m-1)\)。若两条路径同时走了 \((1,2)\) 和 \((n,m-1)\),\((2,1)\) 和 \((n-1,m)\),那就是有交。
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3002;
const int mod=1e9+7;
ll f[N][N];
int a[N][N];
int n,m;
ll a1,a2,a3,a4;
ll solve(int bx,int by,int ex,int ey){
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++) f[i][j]=0;
}
f[bx][by]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]==0) f[i][j]=0;
else f[i][j]=((f[i][j]+f[i-1][j])%mod+f[i][j-1])%mod;
}
}
return f[ex][ey];
}
int main(){
char s;
scanf("%d%d",&n,&m);
s=getchar();
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++){
s=getchar();
if(s=='.') a[i][j+1]=1;
else a[i][j+1]=0;
}
s=getchar();
}
a1=solve(1,2,n-1,m);
a2=solve(2,1,n,m-1);
a3=solve(1,2,n,m-1);
a4=solve(2,1,n-1,m);
printf("%lld\n",((a1*a2%mod-a3*a4%mod)%mod+mod)%mod);
return 0;
}
启示:
- cin被卡的时候可以换成getchar,注意输入换行。
- lgv引理。对于路径不相交问题,试着对它们的起点终点进行匹配。