凸轮题
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);
}
分糖果
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;
}