snnu1111(子序列求和)
1111: 子序列求和
Time Limit: 3 Sec Memory Limit: 64 MBSubmit: 10 Solved: 2
[Submit][Status][Web Board] [Edit]
Description
给出n个数字,分别为1,2,3,……,n。从中选出t个数字,且这t个数字和为x的方案数为ft,x 。给出m。输出下面的值:
数据满足n <= 20000,m <= min(10, n)。
Input
第一行输入T表示测试数据的行数。接下来T行,每行两个数字n, m。T <= 20。
Output
对于每组测试数据,输出一行。
Sample Input
1 4 2
Sample Output
64
HINT
Source
用d[t][x]表示t个数字和为x的方案数,且这t个数字中的最大数字不超过n。
核心思想 : 给d[t][x]中的每个数字减掉一,变成d[t][x-t],d[t][x]应该等于d[t][x-t].
如果这t个数字中的最小数字为1的话,减去1,最小数字就变为0,相当于t-1个数字,d[t-1][x-t]
,然而这两种情况都未考虑,最大数字不超过n的情况,假设d[t][x-t]中最大数字为n,给这t个数字每个加上1,
那么最大数字就为n+1,超过n,所以,要去掉n+1,这个最大数字,减掉这种情况,相当于减掉d[t-1][x-t].
还有一个坑就是A+B-C,由于要取模,所以A+B可能小于C,(A+B-C+MOD)%MOD;
(d%MOD+MOD)%MOD;
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> using namespace std; #define maxn 21000 #define LL long long #define MOD 1000000007 int n,m; LL answer; LL d[15][maxn*15]; LL f[15]; void init() { memset(d,0,sizeof(d)); for(int i=1;i<15;i++) f[i]=1; } LL pow(int a,int b) { LL ans=1; for(int i=1;i<=b;i++) { ans=(ans*a)%MOD; } return ans; } void solve() { for(int i=1;i<=n;i++) d[1][i]=1; f[1]=pow(2,n); // cout<<f[1]<<endl; for(int t=2;t<=m;t++) { for(int x=1;x<(t*n);x++) { d[t][x]=(d[t][x-t]+d[t-1][x-t]-d[t-1][x-(n+1)] + MOD)%MOD; f[t]=(f[t]*(d[t][x]+1))%MOD; //printf("%d %d %d\n",t,x,d[t][x]); } //最小值要么为1,要么不为1, //当最小值为1时,最大值可能为n,需要减去d[t-1][x-(n+1)]; //当最小值不为1,最大值也可能为n,需要减去d[t-1][x-(n+1)]; //由于这两种情况互斥,所以最多只需要减1次 //cout<<endl; // while(f[t]<0) //cout<<f[t]<<endl; } answer=0; for(int t=1;t<=m;t++) { // printf("f %d %d\n",t,f[t]); answer=(answer+f[t])%MOD; answer=answer%MOD; } // while(answer<0) printf("%lld\n",answer%MOD); } int main() { // cout<<(-5)%3<<endl; int t; scanf("%d",&t); while(t--) { init(); scanf("%d%d",&n,&m); solve(); } return 0; }