zoukankan      html  css  js  c++  java
  • JZOJ3056 数字 题解

    JZOJ3056 数字 题解


    Description
    一个数字被称为好数字当他满足下列条件:

    1. 它有2*n个数位,n是正整数(允许有前导0)
    2. 构成它的每个数字都在给定的数字集合S中。
    3. 它前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;
    }
    

    好难啊

  • 相关阅读:
    .37-浅析webpack源码之事件流make(4)
    .36-浅析webpack源码之Parser类
    .35-浅析webpack源码之babel-loader入口文件路径读取
    .34-浅析webpack源码之事件流make(3)
    浅探element-ui2组件源码之upload
    .33-浅析webpack源码之doResolve事件流(5)
    .32-浅析webpack源码之doResolve事件流(4)
    .31-浅析webpack源码之doResolve事件流(3)
    .30-浅析webpack源码之doResolve事件流(2)
    windows server 2016 支持多用户远程登录
  • 原文地址:https://www.cnblogs.com/zjlcnblogs/p/8551409.html
Copyright © 2011-2022 走看看