https://www.cnblogs.com/hzoi-DeepinC/articles/11102979.html
Description
Input
一行两个整数N,K
Output
一行为答案。
Sample Input
3 2
Sample Output
6
Sample Explanation
假设原集合为{A,B,C}
则满足条件的方案为:{AB,ABC},{AC,ABC},{BC,ABC},{AB},{AC},{BC}
Hint
对于100%的数据,1≤N≤1000000;0≤K≤N;
写题解不只是为了写题解,重在讲思路,想直接看正解的兄台自行跳跃阅读。
抱歉我脑子略笨,的确是没能一下想到正解,第一思路是一个n2的暴力
70%算法
虽说是暴力,但其实思路并不太好想,因为乍一下没有上正轨,所以最后的思维量几乎比std还大
然而只有70分,没人会体恤你有多笨QAQ
假如原问题的含义以函数f(n,k)表示,即n元素交集为k的方案数
我们可以认为这个过程分为两步:f(n,k)=C(n,k)*f(n-k,0)
第一步是从n个元素中选出k个C(n,k),其次要任意选集合使它们的交集恰好为这k个元素
那么我们可以确定,最后被选出的集合中,每个集合都含有这k个元素,而其余元素的交集为空集:f(n-k,0)
如:{a,b,c}三个元素,k=1时,假如你选定了a,那么可能出现的集合有:{a},{a,b},{a,c},{a,b,c}
去掉你选中的元素a,剩下的是Ø,{b},{c},{b,c},你要在这几个集合中任选,使它们交集为空
一种可能的选法是{a},{a,b},{a,b,c},去掉a后剩下Ø,{b},{b,c},它们的交集的确为空
看起来“其余元素交集为空”这个问题会更好解一些,即f(s,0)。
假如真的是这样,那么问题就化简成了f(n,k)=C(n,k)*f(n-k,0)
这样所有的问题都可以依赖f(n-k,0)来求解了。我们只需要求出f(x,0)这个数列
我一直在尝试找到这个数列的规律,于是我手模了许多点,得到了f(n,k)的表格
//Update:感谢starsing大神更正数据,f(4,0)应该是64594
这个表格里貌似规律很多,但是f(x,0)真的有规律么:2,10,218,64594...
我没发现。。。但是我发现了最后一行:所有选法是22^n-1,因为一共2n个集合,每一个集合都可以选或不选
但不能都不选,所以要-1。
现在我们就能得到f(x,0)了,假如我们已知所有的f(1,0),f(2,0)....f(x-1,0),用总数减去∑i=1->xf(x,i)即∑i=1->xC(x,x-i)*f(x-i,0)
这样是n2的,在求f(n,k)的过程中我们可以求出所有的f(m,s)值(m<=n)
但是大多数f值都没有用,存下它们会爆内存,于是用完直接覆盖就行,保留f(x,0)即可
1 #include<cstdio> 2 #define int long long 3 const long long mod=1000000007; 4 long long fac[1000005],inv[1000005],invv[1000005],x,y,n,k,f0[1000005],f[1000005]; 5 long long pow(long long b,long long t,long long modd,long long ans=1){ 6 for(;t;t>>=1,b=b*b%modd)if(t&1)ans=ans*b%modd; 7 return ans; 8 } 9 signed main(){ 10 scanf("%lld%lld",&n,&k); 11 fac[0]=inv[0]=invv[0]=f0[0]=invv[1]=inv[1]=fac[1]=1;f0[1]=2; 12 for(int i=2;i<=n;++i)fac[i]=fac[i-1]*i%mod,invv[i]=(-mod/i*invv[mod%i])%mod,inv[i]=inv[i-1]*invv[i]%mod; 13 for(int i=1;i<=n;++i){ 14 f0[i]=(pow(2,pow(2,i,mod-1),mod)-1+mod)%mod; 15 for(int j=1;j<=i;++j)f[j]=fac[i]*inv[j]%mod*inv[i-j]%mod*f0[i-j]%mod,f0[i]=(f0[i]-f[j]+mod)%mod; 16 f[0]=f0[i]; 17 } 18 printf("%lld\n",(f[k]+mod)%mod); 19 }