容斥做题笔记

凸轮题

\[F[n]=\sum \limits _{i\subsetneqq n} G_i*F_{n\oplus i}*(|i|!)*(-1)^{|i|+1}/|n| \]

Description

现在有一个 n 个节点的图,n*(n-1)/2 条边的无向完全图,图中不可能包含自环和重边.每条边没有边权.
我们希望给这个图染色,从而使每条边连接的点颜色不同.
然而现在采用了坠新的64位超仿真彩色显示,每个点的颜色是渐变的一个区间,这使得染色的方案数大大增多.
不过 Komachi并没有放弃,他还想知道个图的可行染色方案数在模1000000007下是多少.

化简题意

给定n个集合,每个集合中有若干元素.
现在要求在每个集合中取出一个元素,要求每个集合中取出的元素均不同,求方案数.
\(n\leq 15\)

Solution

  • 考虑一种\(3^n\)的做法.
  • 首先记G若干集合取出元素皆相同的方案数.
  • 然后记F若干集合取出元素均不相同的方案数.
  • 考虑转移.
  • 发现对于一个\(G[i]*F[n\oplus i]\),表示的颜色最多增加一个.
  • 对于最后的一种不合法方案:\(111123\) 是不能记录答案的
  • 现在研究那些状态可以转移过来: \(100023, 010023, 001023, 000123, 000023\) 对于前4种,方案数可以表示为i,i表示最后重复数字的个数. 而最后一种只能从一种状态转移过来.
  • 所以在计算贡献时,有乘\(|i!|\),用于消元重复方案.
  • 再分析合法情况\(123456\),只能从\(023456,103456,120456,123056,123406,123450\). 就是\(|n|\)种方案.
  • 所以最后要除以\(|n|\)
  • 其实上面的代码还有一种优化方法,虽然已经不在容斥的考虑范围之内.
  • 发现上面的公式是一个典型的子集卷积,所以可以套用\(FWT\)\(O(2^nn^2)\)的复杂度下求解.
  • 大致流程是,记\(F_{x,n}\),第一位表示数值为n,x为数值为1的数位个数.
  • 所以F中有数值的也只有n个.
  • 然后计算\(F_{x,n}\)时就枚举\(F_{0...x-1}\)和对应的\(G\)乘一下.
  • 然后把非对应位的数值给消掉.
fwt(F[0],len+1,1);
FOR(i,0,n)fwt(G[i],len+1,1);
FOR(i,1,n){
    int *A=F[i];
    FOR(k,0,i-1){
        int *B=F[k],*C=G[i-k];
        FOR(j,0,len)if(B[j]&&C[j])A[j]=(A[j]+(LL)B[j]*C[j])%P;
    }
    fwt(A,len+1,-1);
    FOR(j,0,len)Cnt[j]!=i?A[j]=0:A[j]=(LL)A[j]*Inv[i]%P;
    if(i!=n)fwt(A,len+1,1);
}

分糖果

\[A[0]=Tot-A[1]+A[2]-A[3]+... \]

Description

N 个小朋友围成一圈,你有无穷个糖果,想把其中一些分给他们。
从某个小朋友开始,我们顺时针给他们标号为 1 ~ N。第 i 个小朋友可以得到至多 a[i],至少 1 个糖果。
问有多少种分配方案使得每一对相邻的小朋友拿到的糖果数不同。答案对 \(10^9+7\)取模。
\(n\le 10^6,a[i]\le 10^9\)

Solution

  • 对于这道题,先考虑链的情况

  • \(Dim\) \(f[i]\)\([1,i]\) 满足条件的方案数,那么由\(f[i-1]\)递推到\(f[i]\)的公式为\(f[i-1]*A[i]\),但是这样显然会有重复的,而且只存在第i项和第i-1项重复的情况,那就减去\(f[i-2]*min(A[i],A[i-1])\),但是这样又会把第i项,第i-1项,第i-2项都相同的情况给减掉,所以再加上。 所以得到\(f[i]=\sum \limits _{j<i} f[j]*min(A_{j+1...i})*(-1)^{i-j-1}\)

  • 这样就得到了一个\(O(n^2)\)的容斥做法。

  • 然后发现每次的最小值都和当前的i相关,所以可以用单调栈维护一个最小值递增的序列,同时记录贡献就可以得到答案了。

  • 发现这样算出的答案中还是会包含首尾相同的情况。

  • 所以还要继续容斥,为了让分类讨论较为方便,把最小的\(A_i\)放在前面,那么首尾相同时就等于\(f[i-1]\)的方案数,然后再用上面的公式进行转移,即\(f[i]-f[i-1]+f[i-2]-...f[2]\)

Code

#include<cstdio>
#define FOR(i,x,y) for(int i=(x),i##_END=(y);i<=i##_END;++i)
#define DOR(i,x,y) for(int i=(x),i##_END=(y);i>=i##_END;--i)
typedef long long LL;
const int M=1000005;
const int P=1000000007;
inline bool chk_mi(int &x,int y){return x>y?x=y,true:false;}
inline bool chk_mx(int &x,int y){return x<y?x=y,true:false;}

int A[M<<1],n;
int Sum[M],dp[M];
struct node{int l,r,mi,s;}Sk[M];
inline void del(int &x,const int &y){x-=y;if(x<0)x+=P;}
inline void add(int &x,const int &y){x+=y;if(x>=P)x-=P;}
inline void deal(int &x){if(x<0)x+=P;if(x>=P)x-=P;}
inline void Rd(int &res){
	res=0;char c;
	while(c=getchar(),c<48);
	do res=(res<<3)+(res<<1)+(c&15);
	while(c=getchar(),47<c);
}

int main(){
	
	int mx=1e9,id=0;
	scanf("%d",&n);
	FOR(i,1,n){
		Rd(A[i]);
		if(chk_mi(mx,A[i]))id=i;
		A[i+n]=A[i];
	}
	FOR(i,1,n)A[i]=A[id+i-1];
	
	int top=0,Tot=P-A[1];

	FOR(i,1,n){
		
		if(i&1)del(dp[i],Tot);
		else add(dp[i],Tot);
		
		Sum[i]=Sum[i-1];
		if(i&1)add(Sum[i],dp[i]);
		else del(Sum[i],dp[i]);
		
		int l=i-1; 
		while(top&&Sk[top].mi>A[i+1]){
			del(Tot,Sk[top].s);
			--top;
			l=Sk[top].r;
		}
		
		int s=(LL)(Sum[i]-Sum[l])*A[i+1]%P;
		deal(s);
		Sk[++top]=(node){l+1,i,A[i+1],s};
		add(Tot,s);
	}
	
	LL Ans=0;
	FOR(i,2,n){
		if((n-i)&1)Ans-=dp[i];
		else Ans+=dp[i];
	}
	printf("%lld\n",(Ans%P+P)%P);
	
	return 0;
}
posted @ 2018-09-17 11:07  Zerokei  阅读(249)  评论(0编辑  收藏  举报