JZOJ 3056. 【NOIP2012模拟10.27】数字

训练时突然发现了这么一道题:

一个数字被称为好数字当他满足下列条件:
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。

一道很明显的DP。虽然楼主一开始就设错了方程式……
我们设f[i][j]为构造的,长度为i且序列所有数字和为j的方案数。
不难得:f[i][j]=sigma(i=1~N,j=0~i*Max_Num,k=1~S) f[i-1][j-num[k]]
当我们重铸序列后,得出的方案数就能算答案了。

考虑第一种情况:(要求前n为与后n为和相同)因为序列是2*n的,所以只要将两串 和相等且均为n的序列拼在一起就能符合要求。
Ans+=sigma(i=0~N*Max_Num) f[n][i]^2

考虑第二种情况:(要求奇偶数位上的数和相等)同样是有2个n序列构成的,所以对答案的贡献与第一种相同。
Ans+=sigma(i=0~N*Max_Num) f[n][i]^2

但是方案一和二是有重复情况的!

我们可以尝试构造一个序列使他既符合一又符合二。一是两边相等,二是奇偶相等。不妨设左边的奇数为s1,偶数为s2,那么右边的奇数即为s2,偶数为s1,可以枚举s1和s2,算出重复的方案数。
Ans-=sigma(i=0~|(N+1)/2|*Max_Num,j=0~|N/2|*Max_Num) f[|(N+1)/2|][i]^2*f|[N/2|][j]^2

注:|X|为floor(X)和trunc(X).

Code From Zhongjunquan

#include
#include
#include
#define maxn 999983
using namespace std; 

int f[1001][9001],tot,n,num[11];

int sqr(int n)
{
    long long t=(long long)n;
    int p=int((t*t)%maxn);
    return p;
}

int main()
{
    scanf("%d\n",&n);
    char ch=getchar();
    while(ch>='0' && ch<='9')
    {
        num[++num[0]]=ch-48;
        ch=getchar();
    }
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=i*9;j++)
        {
            tot=0;
            for(int k=1;k<=num[0];k++)
                if(j>=num[k])tot=(tot+f[i-1][j-num[k]])%maxn;
            f[i][j]=tot;
        }
    }
    int a,b,c,odn,nodn;
    a=b=c=0;
    for(int i=0;i<=n*9;i++)a=(a+sqr(f[n][i]))%maxn;
    odn=(n+1)/2;nodn=n-odn;
    for(int i=0;i<=odn*9;i++) b=(b+sqr(f[odn][i]))%maxn;
    for(int i=0;i<=nodn*9;i++) c=(c+sqr(f[nodn][i]))%maxn;
    long long t=(long long)b*(long long)c;
    printf("%d",(a+a-int(t%maxn)+maxn)%maxn);
}

你可能感兴趣的:(题目分析)