JZOJ3056 数字 题解
Description
一个数字被称为好数字当他满足下列条件:
- 它有2*n个数位,n是正整数(允许有前导0)
- 构成它的每个数字都在给定的数字集合S中。
- 它前n位之和与后n位之和相等或者它奇数位之和与偶数位之和相等
例如对于n=2,S={1,2},合法的好数字有1111,1122,1212,1221,2112,2121,2211,2222这样8种。
已知n,求合法的好数字的个数mod 999983。
Input
第一行一个数n。
接下来一个长度不超过10的字符串,表示给定的数字集合。
Output
一行一个数字表示合法的好数字的个数mod 999983。
Sample Input
2
0987654321
Sample Output
1240
Hint
对于20%的数据,n≤7。
对于100%的.据,n≤1000,|S|≤10。
是一道方案数统计类型的题目。
我们需要结合容斥原理来做这道题。由于要求的数可以满足两个条件A、B中的任意一个,我们可以先求出满足A条件的个数,再求出满足B条件的个数,最后求出同时满足A、B的个数,A+B-A∩B就是答案。
A条件:该数列的前n位之和等于后n位之和。
设f[i][j]表示从集合S中取出的数,组成i位,和为j的方案数。递推方程:
f[i][j] += f[i - 1][j - num[k]]
num就是集合S。
那么我们可以枚举前n位和为i的方案即所有的f[n][i],由于后n位与前n位和相等,那么后n位的方案数也是f[n][i],两者相乘就是:前n位和=后n位和=i 的方案数。
B条件:该数列偶数位上的和等于奇数位上的和。
偶数位上的所有数长度为n,奇数位上的所有数长度也是n,于是可以把奇数序列当做一个单独的序列,偶数序列当做一个单独的序列,那么方案数就和A的方案数一样了。所以我们求的时候可以直接加2 * f[n][i] * f[n][i]。
同时满足A条件与B条件:
设该数列的奇数位序列为X,偶数位序列为Y。X的前半段和为a,后半段和为b;Y的前半段和为c,后半段和为d。若a = d, b = c,可推出a + b = c + d与a + c = b + d,前一个式子表明该数列的奇数位和等于偶数位和,后一个式子表明数列前半段的和等于后半段的和(仔细思考!奇数序列的前半段加上偶数序列的前半段就是整个序列的前半段!)。X的前半段和Y的后半段长度显然是(n + 1) / 2,也就是1~n之间奇数的个数为(n + 1) / 2,所以我们可以枚举i,累加所有的f[(n + 1) / 2][i]。偶数序列同理,枚举i,累加所有的f[n / 2],把两个累加的和乘起来就是这个并集的方案数。把总方案数减掉并集的方案数即可。
结合代码理解:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
typedef long long ll;
const int N = 1e3 + 3;
const ll P = 999983;
int n, len;
ll anssum = 0, anseven = 0, ansodd = 0, f[N][N * 9];
char str[15];
int num[15];
int main()
{
scanf("%d%s", &n, str + 1);
len = strlen(str + 1);
for (int i = 1; i <= len; i++) num[i] = str[i] - '0';
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n * 9; j++)
for (int k = 1; k <= len; k++)
if (j - num[k] >= 0)
f[i][j] = (f[i][j] + f[i - 1][j - num[k]]) % P; //递推求出f数组
for (int i = 0; i <= n * 9; i++)
anssum = (anssum + 2 * f[n][i] % P * f[n][i]) % P; //求出满足A条件的和满足B条件的方案数
int len1 = (n + 1) / 2, len2 = n / 2; //求出两序列长度
for (int i = 0; i <= len1 * 9; i++)
anseven = (anseven + f[len1][i] % P * f[len1][i]) % P; //求出奇数序列的方案数
for (int i = 0; i <= len2 * 9; i++)
ansodd = (ansodd + f[len2][i] % P * f[len2][i]) % P; //求出偶数序列的方案数
printf("%lld
", (anssum - anseven * ansodd % P + P) % P); //总方案减去两者相乘,注意要+P,防止出现负数
return 0;
}
好难啊