01背包的倍数或余数相关的问题,P2946 [USACO09MAR]Cow Frisbee Team S
P2946 [USACO09MAR] Cow Frisbee Team S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
老唐最近迷上了飞盘,约翰想和他一起玩,于是打算从他家的 N 头奶牛中选出一支队伍。
每只奶牛的能力为整数,第 i 头奶牛的能力为Ri 。飞盘队的队员数量不能少于 11、大于N。一支队伍的总能力就是所有队员能力的总和。
约翰比较迷信,他的幸运数字是 F ,所以他要求队伍的总能力必须是 F 的倍数。请帮他
算一下,符合这个要求的队伍组合有多少?由于这个数字很大,只要输出答案对 108108 取模的值。
输入格式
第一行:两个用空格分开的整数:N 和 F。
第二行到N+1 行:第i+1 行有一个整数Ri ,表示第 i 头奶牛的能力。
输出格式
第一行:单个整数,表示方案数对 108108 取模的值。
输入输出样例
输入 #1复制
4 5 1 2 8 2
输出 #1复制
3
说明/提示
数据范围与约定
- 对于 100%100% 的数据,11≤N≤2000,1≤F≤1000 ,1≤Ri≤105 。
题目中给的数据范围:对于 100% 的数据,1≤N≤2000,1≤F≤1000 ,1≤Ri≤1e5;
如果按照模板写这道题就是:有N中物品,最大的重量为N*Ri,分配一个数组f[N][N*Ri],
数组f表示前i个物品重量为j的方案数
将说有的重量类型算出来,在从中找出F的倍数,累加即可得到结果;
但这显然是行不通的,因为我们无法分配这么大的数组
所以我们找出的道题的性质,然后加以利用:
性质1:这道题目要求寻找出能组合成F的倍数的方案数,而(a+b)%F==(a%F+b%F)%F;
因此我们就可以将f[N][N*Ri]改写成f[N][F],
横坐标表示%F的余数,f的表示前i头奶牛的能力值和%F==j的方案有几种;
则状态转移方程为:
f[i][j]+=(f[i-1][j]+f[i-1][(j-cow[i]+F)%F])%1e8;
这里有两个需要注意的地方
注1:f[i-1][(j-cow[i]+F)%F]中(j-cow[i]+F)是有可能前一个状态在j的右边,即j-cow[i]可能小于零;
注2:初始化的时候所有f[i][arr[i]]=1;
#include<iostream>
#include<set>
#include<vector>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<map>
#include<math.h>
using namespace std;
typedef long long LL;
const int N = 1e4;
const int mod = 1e8;
int f[N][N], arr[N];
int n, m;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
arr[i] %= m;
}
for (int i = 1; i <= n; i++) {
f[i][arr[i]] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < m; j++) {
f[i][j] += (f[i - 1][j] + f[i - 1][(j - arr[i] + m) % m]) % mod;
}
}
/*for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
cout << f[i][j] << " ";
}
cout << endl;
}*/
printf("%d\n", f[n][0]);
return 0;
}