[bzoj4314] 倍数?倍数! 解题报告
2016-05-18 19:27
323 查看
感觉完全不会做。。看讨论区里有大爷用母函数做的。。完全不会母函数,所以想看看官方题解。。但是官方题解要登录topcoder,注册还得翻墙,然后还是英文,搞了好久终于看懂了。感觉每一步都非常神。。
我们要求的是集合的个数,集合是无序的,这并不好求。我们可以变无序为有序,先求有序集合的数量然后除以k!。
考虑一个元素互不相同的长为k-1的序列,如果说我们要求长为k的序列mod n=0的话,实际上最后一个元素就已经被确定了。所以我们就可以写出:长为k的序列所有元素互不相同和mod n=0的序列方案数=长为k-1的序列所有元素互不相同的序列方案数-长为k的序列有且仅有一对元素相同且mod n=0的方案数。
但问题是,后者依然不易求。不过,按照类似的思路,我们考虑一下有且仅有一对元素相同且mod n=0的序列,我们把这一对元素放到最后,设前k-2个元素的和为S,则最后一对元素就是S+2x=0 mod n的解。这就是一个经典的丢番图方程问题了。显然,当且仅当(2,n)|S时有解,且解有(2,n)个,所以问题就被神奇的化归到了mod (2,n)=0时的情况!
所以我们可以设f(k,d,a)表示在[0,n)中选k个不同的数构成序列,要求它们mod d=0,且最后一个数在序列的最后重复了a遍的方案数。考虑转移,前k-1个数的和为S,则S+ax=0 mod d当且仅当S mod (a,d)=0,且x的循环节长度是d(a,d),所以每一种S对应的x的数量为nd(a,d),那么我们就可以算出重复a或a+1遍的方案数是nd(a,d)f(k−1,(a,d),1),可能会出现重复了a+1遍的情况是因为我们在后面添的数可能和前面的数重复了,所以我们要减去这种讨厌的情况,这种情况的方案数是(k−1)f(k−1,d,a+1)。注意到我们的状态表示的是最后一个数重复a遍(因为我们转移的时候总是试图在最后加一个数),因为考虑每种非法情况,它实际上可以被每一种重复的数在最后的情况交换位置得到,而一共有(k-1)个位置可供交换,所以要乘以k-1。
再考虑一下边界情况,当k=1时,答案当然就是nd;当d=1时,答案就是Akn。
所以总的转移就是f(k,d,a)=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪ndAknnd(a,d)f(k−1,(a,d),1)−(k−1)f(k−1,d,a+1),k=1,d=1,k>1&d>1
时间复杂度是O(k3)的。因为a随着k减1只会增加1,而d除了等于n就只会等于小于等于a的数,所以它们的范围都是O(k)的。
不过注意到实际上这个上界是很松的,因为光a、k这两维实际上就应该是k(k+1)2之类的东西,而d实际上更远远不足k,因为d必须要满足是n的因子,而n在1001内的因子显然不会太多。所以这个时间复杂度实际上非常虚。
但是空间呢?注意到实际上我们只需要保存f(k,d,1)即可,因为f(k,d,a)(a>1)只会被调用一次。所以空间只需要O(k2)。
总结:
①无序与有序之间的相互转化经常非常有用!
②dp时如果感觉状态不够用可以尝试大胆的扩展。
我们要求的是集合的个数,集合是无序的,这并不好求。我们可以变无序为有序,先求有序集合的数量然后除以k!。
考虑一个元素互不相同的长为k-1的序列,如果说我们要求长为k的序列mod n=0的话,实际上最后一个元素就已经被确定了。所以我们就可以写出:长为k的序列所有元素互不相同和mod n=0的序列方案数=长为k-1的序列所有元素互不相同的序列方案数-长为k的序列有且仅有一对元素相同且mod n=0的方案数。
但问题是,后者依然不易求。不过,按照类似的思路,我们考虑一下有且仅有一对元素相同且mod n=0的序列,我们把这一对元素放到最后,设前k-2个元素的和为S,则最后一对元素就是S+2x=0 mod n的解。这就是一个经典的丢番图方程问题了。显然,当且仅当(2,n)|S时有解,且解有(2,n)个,所以问题就被神奇的化归到了mod (2,n)=0时的情况!
所以我们可以设f(k,d,a)表示在[0,n)中选k个不同的数构成序列,要求它们mod d=0,且最后一个数在序列的最后重复了a遍的方案数。考虑转移,前k-1个数的和为S,则S+ax=0 mod d当且仅当S mod (a,d)=0,且x的循环节长度是d(a,d),所以每一种S对应的x的数量为nd(a,d),那么我们就可以算出重复a或a+1遍的方案数是nd(a,d)f(k−1,(a,d),1),可能会出现重复了a+1遍的情况是因为我们在后面添的数可能和前面的数重复了,所以我们要减去这种讨厌的情况,这种情况的方案数是(k−1)f(k−1,d,a+1)。注意到我们的状态表示的是最后一个数重复a遍(因为我们转移的时候总是试图在最后加一个数),因为考虑每种非法情况,它实际上可以被每一种重复的数在最后的情况交换位置得到,而一共有(k-1)个位置可供交换,所以要乘以k-1。
再考虑一下边界情况,当k=1时,答案当然就是nd;当d=1时,答案就是Akn。
所以总的转移就是f(k,d,a)=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪ndAknnd(a,d)f(k−1,(a,d),1)−(k−1)f(k−1,d,a+1),k=1,d=1,k>1&d>1
时间复杂度是O(k3)的。因为a随着k减1只会增加1,而d除了等于n就只会等于小于等于a的数,所以它们的范围都是O(k)的。
不过注意到实际上这个上界是很松的,因为光a、k这两维实际上就应该是k(k+1)2之类的东西,而d实际上更远远不足k,因为d必须要满足是n的因子,而n在1001内的因子显然不会太多。所以这个时间复杂度实际上非常虚。
但是空间呢?注意到实际上我们只需要保存f(k,d,1)即可,因为f(k,d,a)(a>1)只会被调用一次。所以空间只需要O(k2)。
#include<cstdio> #include<iostream> using namespace std; #include<algorithm> #include<cstring> const int Mod=1000000007; typedef long long LL; const int N=1e9+5,K=1000+5; LL pow(LL prod,int x){ LL ans=1; for(;x;x>>=1){ if(x&1)ans=ans*prod%Mod; prod=prod*prod%Mod; } return ans; } int gcd[K][K],gn[K]; int n; LL f[K][K]; LL array[K]; LL dfs(int k,int d,int a){ int g=d==n?gn[a]:gcd[a][d]; if(k==1)return n/d*g; if(d==1)return array[k]; if(a==1&&d<=1000){ if(f[k][d]==-1)f[k][d]=((n/d*g*dfs(k-1,g,1)-(k-1)*dfs(k-1,d,a+1))%Mod+Mod)%Mod; //printf("f(%d,%d,%d)=%I64d\n",k,d,a,f[k][d]); return f[k][d]; } LL ans=((n/d*g*dfs(k-1,g,1)-(k-1)*dfs(k-1,d,a+1))%Mod+Mod)%Mod; //printf("f(%d,%d,%d)=%I64d\n",k,d,a,ans); //cout<<n/d*g*dfs(k-1,g,1)<<endl; return ans; } int main(){ int k; scanf("%d%d",&n,&k); if(n<k){ puts("0"); return 0; } for(int i=1;i<=k;++i){ gcd[i][0]=gcd[0][i]=i; for(int j=1;j<=i;++j)gcd[i][j]=gcd[j][i]=gcd[i%j][j]; gn[i]=gcd[i][n%i]; } array[0]=1; for(int i=1;i<=k;++i)array[i]=array[i-1]*(n-i+1)%Mod; memset(f,-1,sizeof(f)); LL ans=dfs(k,n,1); //7cout<<ans<<endl; for(int i=k;i;--i)ans=ans*pow(i,Mod-2)%Mod; cout<<ans<<endl; }
总结:
①无序与有序之间的相互转化经常非常有用!
②dp时如果感觉状态不够用可以尝试大胆的扩展。
相关文章推荐
- 访问jsp报HTTP Status 500 - java.lang.NullPointerException的解决方法
- 组合组件のCusRelativeLayout(Android-自定义相对布局)
- windows10相关文章
- Windows 64位下安装Redis详细教程
- semiautomatic annotated tools
- 两个栈实现一个队列
- 质因数分解
- swift学习笔记之函数形参,返回值以及函数类型
- android中bitmap
- linux命令之sed, awk, grep, cut
- WEB+APP中图表应用
- hdu 1520 Anniversary party(树形dp)
- 第六届蓝桥杯决赛C++B组第一题 积分之谜
- Build Settings
- Java 队列的遍历
- 膨胀和腐蚀的例子
- 第十二周项目2:实现复数类中的运算符重载(2)
- javascript日期格式化(转字符串)
- bash内置命令
- iOS block 基本用法总结