最近才接触到了一些dp最后容斥的题目,还经常和状压,概率期望啥的一起搞事情。由于之前基本没做过类似的题目,就被虐飞了,所以就做了一些题目。。
一般都是比如答案是要求“恰好…”,但是我们难以限制成恰好的情况,所以我们可以试图放宽条件,改成类似于“至少…”的状态。这个时候我们就可以先构造出满足至少的情况,其他的就可以随意组合。然后最后我们要统计答案的话就可以上容斥。注意先想好容斥的复杂度。。
还有一些比较迷的容斥模型,比如子集容斥,min-max容斥啊啥的,可能得要手推公式。随便丢点常用的公式咯:
子集容斥: f [ s ] = ∑ t ⊂ s ( − 1 ) ∣ s ∣ − ∣ t ∣ f [ t ] f[s]=\sum_{t\subset s}(-1)^{|s|-|t|}f[t] f[s]=∑t⊂s(−1)∣s∣−∣t∣f[t]
min-max容斥: m a x ( S ) = ∑ t ⊂ s ( − 1 ) ∣ t ∣ − 1 m i n ( t ) max(S)=\sum_{t\subset s}(-1)^{|t|-1}min(t) max(S)=∑t⊂s(−1)∣t∣−1min(t)
部分题解就直接丢在这下面了…如果有的话
Atcoder
位置与值一共是2n个点,可以构造一个这样的二分图,如果我们在相差k的两点间连线。yy画一下这个图,那么这个图就相当于被拆成了几条链。我们把这些链放在一起考虑。
显然答案就是要求没有一条边匹配的方案数。然而这个并不好直接统计。所以我们不妨这样来考虑,设 G [ i ] G[i] G[i]表示至少有i条边匹配上的方案数,那么我们就强制选i条边即可,其他的位置随便乱排。这样就至少有i个不合法的方案。
那我们就对搞下来的这个序列进行dp。记 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示到第i个点选取了j条边且第i-1个点和第i个点之间的边有没有选时的方案数。记 F [ i ] F[i] F[i]表示选取了至少i条边的方案数,那么显然我们有 F [ i ] = ( f [ 2 n ] [ i ] [ 0 ] + f [ 2 n ] [ i ] [ 1 ] ) ∗ ( n − i ) ! F[i]=(f[2n][i][0]+f[2n][i][1])*(n-i)! F[i]=(f[2n][i][0]+f[2n][i][1])∗(n−i)!。
我们可以认同这个式子
G [ i ] = F [ i ] − ∑ j = i + 1 n G [ j ] ( j i ) G[i]=F[i]-\sum_{j=i+1}^n G[j]\binom j i G[i]=F[i]−j=i+1∑nG[j](ij)
移项即可得到
F [ i ] = ∑ j = i n G [ j ] ( j i ) F[i]=\sum_{j=i}^n G[j]\binom j i F[i]=j=i∑nG[j](ij)
a n s = G [ 0 ] = ∑ i = 0 n ( − 1 ) i F [ i ] ans=G[0]=\sum_{i=0}^n (-1)^i F[i] ans=G[0]=i=0∑n(−1)iF[i]
为什么这样是对的呢?我们推一推系数就会发现,其他每一项的系数相当于是n=i的组合数被做了一次奇偶性分组,并作差,显然除了 G [ 0 ] = ( 0 0 ) = 1 G[0]=\binom 0 0=1 G[0]=(00)=1之外,其他的系数都为0。
#include
#define rg register
using namespace std;
typedef long long ll;
const int maxn=2010,mod=924844033;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,k,lim,tot,ans,fac[maxn],xu[maxn<<1],link[maxn<<1],f[maxn<<1][maxn][2],g[maxn];
inline int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
void get(int id,int op)
{
while(id<=n)
{
xu[++tot]=id;
if(op) xu[tot]+=n;
op^=1;id+=k;
}
link[tot]=1;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
read(n);read(k);lim=n<<1;fac[0]=1;
for(rg int i=1;i<=k;i++) get(i,0),get(i,1);
for(rg int i=1;i<=n;i++) fac[i]=(ll)fac[i-1]*i%mod;
f[0][0][0]=1;link[0]=1;
for(rg int i=1;i<=lim;i++)
for(rg int j=0;j<=n;j++)
{
if(!link[i-1]&&j) f[i][j][1]=f[i-1][j-1][0];
f[i][j][0]=pls(f[i-1][j][0],f[i-1][j][1]);
}
for(rg int i=0;i<=n;i++)
{
g[i]=pls(f[n+n][i][0],f[n+n][i][1]);
if(i&1) ans=dec(ans,(ll)g[i]*fac[n-i]%mod);
else ans=pls(ans,(ll)g[i]*fac[n-i]%mod);
}
printf("%d\n",ans);
return 0;
}
BZOJ
我们用 g [ i ] g[i] g[i]表示长度为i的lis的方案数。求法可以用dp得到,中间用BIT优化一下转移即可。
什么是不合法的方案?那就是最后得出来的lis是由另一个lis删除得到的。
考虑长度为i的lis的多余贡献,首先这i个可以随便删一个,然后其他的删除顺序随意乱排,即 g [ i ] ∗ i ∗ ( n − i ) ! g[i]*i*(n-i)! g[i]∗i∗(n−i)!
#include
#include
#include
#define rg register
#define lowbit(x) ((x)&(-(x)))
using namespace std;
typedef long long ll;
const int maxn=2010,mod=1e9+7;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,tot,ans,a[maxn],b[maxn],fac[maxn],f[maxn][maxn],g[maxn];
inline int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
struct BIT{
int a[maxn];
void clear(){memset(a,0,sizeof(a));}
void add(int p,int v){for(;p<=tot;p+=lowbit(p)) a[p]=pls(a[p],v);}
int query(int p){int res=0;for(;p;p-=lowbit(p)) res=pls(res,a[p]);return res;}
}t;
void input()
{
read(n);fac[0]=1;
for(rg int i=1;i<=n;i++){read(a[i]);b[i]=a[i];fac[i]=(ll)fac[i-1]*i%mod;}
sort(b+1,b+n+1);
tot=unique(b+1,b+n+1)-b-1;
for(rg int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
input();
for(rg int j=1;j<=n;j++)
{
t.clear();
if(j==1) t.add(1,1);
for(rg int i=1;i<=n;i++)
{
f[i][j]=t.query(a[i]);
t.add(a[i],f[i][j-1]);
}
}
for(rg int i=1;i<=n;i++)
for(rg int j=1;j<=n;j++)
g[i]=pls(g[i],f[j][i]);
ans=g[n];
for(rg int i=1;i<n;i++)
{
ans=pls(ans,(ll)g[i]*fac[n-i]%mod);
ans=dec(ans,(ll)g[i+1]*(i+1)%mod*fac[n-i-1]%mod);
}
printf("%d\n",ans);
return 0;
}
BZOJ
按照给出的两个基向量,把所有关键点拆成网格上的点。
然后计算的时候,每条不合法路径只在第一次经过关键点时计算即可。
随便怎么走
( i + j i ) \binom {i+j} i (ii+j)
但是禁止点不允许走。按拓扑序搞成一个序列
设 f [ i ] f[i] f[i]表示走到i个关键点且不经过其他中间的禁止点, g [ i ] [ j ] g[i][j] g[i][j]表示i到j的路径条数
f [ i ] = g [ 1 ] [ i ] − ∑ 1 < j < i f [ j ] ∗ g [ j ] [ i ] f[i]=g[1][i]-\sum_{1<j<i} f[j]*g[j][i] f[i]=g[1][i]−1<j<i∑f[j]∗g[j][i]
#include
#include
#include
#define rg register
using namespace std;
typedef long long ll;
const int maxn=510,maxm=500010,mod=1e9+7;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct vec{
int x,y;
bool operator < (const vec &b)const{return x==b.x?y<b.y:x<b.x;}
int operator / (const vec &b)const
{
return x*b.y-y*b.x;
}
}xi,yi,a[maxn];
int n,tot,fac[maxm],inv[maxm],f[maxn];
inline int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int trans(int &x,int &y)
{
int up,down;vec tmp=(vec){x,y};
up=tmp/xi;down=yi/xi;
if(up%down) return 0;
x=up/down;
if(xi.x) y=(tmp.x-x*yi.x)/xi.x;
else y=(tmp.y-x*yi.y)/xi.y;
return 1;
}
int power(int x,int y)
{
int res=1;
for(;y;y>>=1,x=(ll)x*x%mod)
if(y&1)
res=(ll)res*x%mod;
return res;
}
void input()
{
int u,v,x,y;
read(x);read(y);read(n);
read(xi.x);read(xi.y);read(yi.x);read(yi.y);
if(!trans(x,y)){puts("0");exit(0);}
a[++tot]=(vec){0,0};
for(rg int i=1;i<=n;i++)
{
read(u);read(v);
if(trans(u,v))
{
if(0<=u&&u<=x&&0<=v&&v<=y) a[++tot]=(vec){u,v};
}
}
a[++tot]=(vec){x,y};
sort(a+1,a+tot+1);
fac[0]=1;
for(rg int i=1;i<maxm;i++) fac[i]=(ll)fac[i-1]*i%mod;
inv[maxm-1]=power(fac[maxm-1],mod-2);
for(rg int i=maxm-2;~i;i--) inv[i]=(ll)inv[i+1]*(i+1)%mod;
}
inline int c(int n,int m){return m>n?0:(ll)fac[n]*inv[m]%mod*inv[n-m]%mod;}
inline int calc(int i,int j)
{
if(a[i].x>a[j].x||a[i].y>a[j].y) return 0;
return c(a[j].x-a[i].x+a[j].y-a[i].y,a[j].x-a[i].x);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
input();
f[1]=1;
for(rg int i=2;i<=tot;i++)
{
f[i]=calc(1,i);
for(rg int j=2;j<i;j++) f[i]=dec(f[i],(ll)f[j]*calc(j,i)%mod);
}
printf("%d\n",f[tot]);
return 0;
}