SSY的队列

SSY 的队列

题目描述

SSY是班集体育委员,总喜欢把班级同学排成各种奇怪的队形,现在班级里有N个身高互不相同的同学,请你求出这N个人的所有排列中任意两个相邻同学的身高差均不为给定整数M的倍数的排列总数。

输入格式

共三行:

第一行为N

第二行为N个不同的整数

第三行为M

输出格式

一行,为符合条件的排列总数(答案对1234567891取余数)。

样例

样例输入1

3
-1 0 1
2

样例输出1

2

样例输入2

4
1 2 3 4
3

样例输出2

12

数据范围与提示

20%的数据:N<=11

70%的数据:N<=15

100%的数据:N<=30,M<=1000。

SOLVE

1、暴力,复杂度为 O(N!)。

2、状态压缩动态规划。用一个 N 位整数 i 表示 N 个数是否被用,则 f[k,i]表示当前最后 一个数为 k,已用数集为 i 的合法方案总数。显然,最终结果为∑ f[k, 2 N − 1] N k=1 。时间复杂 度为 O(2N*N2)。

3、进一步可得到如下性质:

性质 1:将数集 N 中的数按模 M 的余数分类,则不同类的任两数相连不会不合法,同 一类的所有数完全等价。 由于同一个类数等价,故对每类可给定一个序,最后再乘以各类数数目的阶乘之积即可, 从而问题转化为对类的考虑。如果有 n 类,则可用 f[k,a1,a2,...,an]表示当前最后一个数为第 k 类,各类数已用去 a1,a2,...,an个数的方案数。 这样已经是实现了很大程度的优化,进一步注意到

性质 2: a1,a2,...,an 的顺序是无关的。 对于任意 i,j,如果 ai=aj,则 f[i,a1,a2,...,an]=f[j,a1,a2,...,an]。 第一个性质的证明由各类相互独立可直接得到。第二个性质的证明可通过 i,j 两类数的 映射使得两个状态中的所有方案一一对应来得到。 从而可以进一步优化状态表示,用 f[i,a1,a2,...,an]表示各类已用去 a1,a2,...,an个数(a1≤a2≤... ≤an),当前最后一个数所在类已用去 i 个数的方案数。 转移时,根据前一个类的已用数目及前一个类的已用数目为该数目的次数,简单统计。

CODE

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int maxn = 35;
const int mod = 1234567891;
const ull bas = 233333;
const int Inf = 0x3f3f3f3f;

int a[maxn], sta[maxn], b[maxn], n, m, tp, mmax, jc[maxn];
bool vis[maxn];
map<ull, int> mp[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

//sta存的是每个组的大小
int dfs(int now, int lat)//lat表示属于哪个组,now表示选了几个数
{
    if(now > n) return 1;
    memset(b, 0, sizeof(b));
    for(int i=1; i<=tp; i++)
    {
        if(i != lat) b[sta[i]]++;//找出和上一个数余数不同的组,从组别编号直接体现
        //b存的是可以被选择的不同大小的组有多少
    }
    //为什么sta[0]和sta[lat]要单独拿出来
    //sta[0]本身就有b的含义没有重复统计
    ull nans = sta[0];
    for(int i=0; i<=mmax; i++)
    {
        nans = nans * bas + b[i];//hash
    }
    nans = nans * bas + sta[lat];
    if(mp[now].find(nans) != mp[now].end()) return mp[now][nans];
    int mans = 0;
    if(sta[0] > 0)
    {
        sta[0]--;
        mans = ((ll)mans + (ll)dfs(now+1, 0)) % mod;
        sta[0]++;
    }
    for(int i=1; i<=tp; i++)
    {
        if(i != lat && sta[i] > 0)
        {
            sta[i]--;
            mans = ((ll)mans + (ll)dfs(now+1, i)) % mod;
            sta[i]++;
        }
    }
    mp[now][nans] = mans;
    return mans;
}

int main()
{
    //freopen("ssy.in", "r", stdin);
    //freopen("ssy.out", "w", stdout);

    n = read();
    for(int i=1; i<=n; i++)
    {
        a[i] = read();
    }
    m = read();
    for(int i=1; i<=n; i++)
    {
        a[i] %= m;
        if(a[i] < 0) a[i] += m;
    }
    int ncnt = 0;
    for(int i=1; i<=n; i++)
    {
        if(vis[i]) continue;
        vis[i] = 1;//数字i有没有被分组
        ncnt = 0;//记录组内有多少数
        for(int j=i; j<=n; j++)
        {
            if(a[i] == a[j])
            {
                vis[j] = 1;
                ncnt++;
            }
        }
        mmax = max(mmax, ncnt);//为什么要找到最大的组
        //为什么ncnt=1的组不需要单独成组
        if(ncnt == 1) sta[0]++;//拿走一个数后剩余0个数的组的个数?
        else sta[++tp] = ncnt;
    }
    jc[0] = 1;
    for(int i=1; i<=n; i++)
    {
        jc[i] = (ll)jc[i-1] * i % mod;
    }
    int ans = 1;
    for(int i=0; i<=tp; i++)
    {
        ans = (ll)ans * jc[sta[i]] % mod;
    }
    //对每类给定一个序(外部顺序),最后再乘各类数目的阶乘之积(内部顺序)
    ans = (ll)ans * dfs(1, 0) % mod;
    printf("%d", ans);

    return 0;
}

 

posted @ 2022-05-15 17:40  Catherine_leah  阅读(158)  评论(0编辑  收藏  举报
/* */