[SCOI2007]排列 题解

题目链接

这道题的其实也可以用数学知识来解,不过需要高中数学

30分

\(s\le5\),那么枚举\(0\)\(99999\),判断每个数字和输入的是否相同就行了

50分

50分没有什么准确的做法,应该是暴力写的好或者是正解写的丑的

Part.1

先说说我们今天比赛时一个不认识的dalao的做法
他把每个序列进行排序构造出\(MAX\)\(MIN\),在这个范围内做30分的做法就行

代码应该可是实现我就不贴了

Part.2

我自己的做法

首先我观察到如果去枚举序列,复杂度会很高

我们考虑枚举d的整数倍,在考虑是否能够被构造出来

怎么判断能否被构造呢

先开一个桶,把序列中每个数字出现的次数存下来

对于每次枚举出来的d的倍数,另开一个桶用相同的方式分解

然后比较两个桶,如果两个同相同这这个数可以被构造出来

然后我发现过\(d=1\)时这种方法会T飞

所以当\(d=1\)时我们考虑组合数给他算出来

首先假设序列长为\(n\),桶用\(t_i\)表示,就可以推出方案数为

\[\frac{A_n^n}{\Pi_{i=0}^{9}A_{ti}^{ti}}\]

然后我又发现假如\(d=2\)时,方案数只和末位奇偶有关

假设序列长为\(n\),桶用\(t_i\)表示,偶数个数为\(m\),就可以推出方案数为

\[\frac{A_{n-1}^{n-1}\times A_m^m}{\Pi_{i=0}^{9}A_{ti}^{ti}}\]

用这种方法同时可以推出\(d=5\)\(d=10\)的情况,另外假如全是0,则只有一种情况

#include 
using namespace std;


const int N = 13;
int T,t[N],a[N],n,m,k,maxn;
int cnt,x;
bool flag;
string s;


inline void work_1()
{
    cnt = 1;
    for(register int i = 2;i <= n;i ++) cnt *= i;
    for(register int i = 0;i <= 9;i ++)
    {
        if(!t[i]) continue;
        for(register int j = 2;j <= t[i];j ++) cnt /= j;
    }
    printf("%d\n",cnt);
}

inline void work_2()
{
    cnt = 0;
    for(register int i = 0;i <= 9; i += 2) 
    {
        if(t[i] > 0) cnt +=t[i];
    }
    if(!cnt)
    {
        puts("0");
        return ;
    }
    for(register int i = 2;i < n;i ++) cnt *= i;
    for(register int i = 0;i <= 9;i ++)
    {
        if(!t[i]) continue;
        for(register int j = 2;j <= t[i];j ++) cnt /= j;
    }
    printf("%d\n",cnt);
}

inline void work_3()
{
    cnt = t[5] + t[0];
    if(!cnt)
    {
        puts("0");
        return ;
    }
    for(register int i = 2;i < n;i ++) cnt *= i;
    for(register int i = 0;i <= 9;i ++)
    {
        if(!t[i]) continue;
        for(register int j = 2;j <= t[i];j ++) cnt /= j;
    }
    printf("%d\n",cnt);
}

inline void work_4()
{
    cnt = t[0];
    if(!cnt)
    {
        puts("0");
        return ;
    }
    for(register int i = 2;i < n;i ++) cnt *= i;
    for(register int i = 0;i <= 9;i ++)
    {
        if(!t[i]) continue;
        for(register int j = 2;j <= t[i];j ++) cnt /= j;
    }
    printf("%d\n",cnt);
}

int main()
{
    //freopen("arrange.in","r",stdin);
    //freopen("arrange.out","w",stdout);
    cin >> T;
    while(T--)
    {
        memset(t,0,sizeof(t));
        cin >> s >> k;
        n = s.size();
        for(register int i = 0;i < n;i ++) t[s[i] - '0'] ++;
        m = n - t[0];
        if(!m)
        {
            puts("1");
            continue;
        }
        if(k == 1)
        {
            work_1();
            continue;
        } 
        if(k == 2)
        {
            work_2();
            continue;
        }
        if(k == 5)
        {
            work_3();
            continue;
        }
        if(k == 10)
        {
            work_4();
            continue;
        }
        cnt = maxn = 0;
        for(register int i = 9;i >= 0;i --)
        {
            for(register int j = 1;j <= t[i];j ++) maxn = (maxn<<3)+(maxn<<1) + i;
        }
        for(register int i = 1;i * k <= maxn;i ++)
        {
            x = i * k; flag = 1; memset(a,0,sizeof(a));
            while(x >= 9) a[x % 10] ++,x /= 10;
            a[x] ++;
            for(register int j = 1;j <= 9;j ++)
            {
                if(t[j] == a[j]) continue;
                flag = 0;
                break;
            }
            if(flag) 
            {
                cnt ++;
            }
        }
        printf("%d\n",cnt);
    }
}

100分

\(s\)的长度不会超过\(10\)

若没有重复的数字,则所有可能情况为\(362880\)

若有所有重复的数字,则所有情况不会超过\(362880\)

\(T\le 15\),所有情况不会超过\(5443200<10^8\)

所以此题正解为勇敢暴力,枚举即可,不用剪枝

暴力枚举所有的序列,枚举序列可以用next_permutation()

#include 
using namespace std;


const int N = 13,mod = 19260817;
int T,cnt,k,len,a[N];
long long sum;
string s;


int main()
{
    cin >> T;
    while(T --)
    {
        cin >> s >> k;
        len = s.size();cnt = 0;
        for(register int i = 1;i <= len;i ++) a[i] = s[i-1] - '0';
        sort(a+1,a+1+len);
        do
        {   
            sum = 0;
            for(register int i = 1;i <= len;i ++) sum = (sum<<3)+(sum<<1)+a[i];
            if(sum % k == 0) cnt ++; 
        }while(next_permutation(a+1,a+1+len));
        printf("%d\n",cnt);
    }
}

你可能感兴趣的:([SCOI2007]排列 题解)