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; }