题意:求按字典序从小到大、第k个满足条件的n的排列。条件:每个数都是波峰/波谷。
注意到如果现在在位置i+1,那么只需要考虑位置i,再之前的位置唯一的影响就是占用了数。
考虑怎么消除这种影响。再之前的位置只是占用了数,那是不是位置i随便放一个都行?
位置i随便放一个数,那么在它之前不小于它的数可以都加上1。
这样只有当前面某个数超过n的时候这个序列才会失效。
然而在不能记录序列方案的情况下,要知道前面有没有数被加到超过n也比较困难。
考虑到前面最大的数在前面有其他被加数的情况下一定会一直被加,
可以新开一个维度来表示前面最大的数的限制。
然后新加一个数的时候就可以随便加了。因为可以假装前面自动调整满足条件。
这是经典套路了。
但是这道题目并不好这么搞,因为它要求输出字典序为k的方案。
那么状态的设计需要跟字典序有关系。
关联状态和字典序的经典方法就是固定第一个数字的状态。
再加上本题对偏序关系的约束。考虑这么分:
uF(i,j)表示长度为i,第一个数在其中为第j小,第一个数比第二个数小的方案数
dF(i,j)表示长度为i,第一个数在其中为第j小,第一个数比第二个数大的方案数
于是有
uF(i,j)=∑dF(i-1,k), j<k
dF(i,j)=∑uF(i-1,k), j>k
预处理uF(i,j), dF(i,j)。
然后求排列。具体的可以容斥也可以计数。
具体的实现上需要考虑的东西还有很多,这个最好还是自己想通吧?
提醒一下,查询答案的部分是每处理一位就确定这一位上的答案的。
第一个和第二个位置是up还是down这个并不用枚举。可以直接确定。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
using namespace std;
long long uF[25][25];
long long dF[25][25];
long long uS[25];
long long dS[25];
bool vis[25];
long long ans[25];
void init() {
uF[1][1] = dF[1][1] = 1;
for (int i = 2; i <= 20; ++i) {
for (int j = 1; j <= i; ++j) {
for (int k = j; k < i; ++k) { dF[i][j] += uF[i-1][k];}
for (int k = 1; k < j; ++k) { uF[i][j] += dF[i-1][k];}
}
}
}
void work(int N, long long C)
{
memset(vis, 0, sizeof(vis));
int ans, pos;
bool Parity;
for (int i = 1; i <= N; ++i) {
if (uF[N][i] >= C) { ans = i, Parity = 1; break;}
else { C -= uF[N][i];}
if (dF[N][i] >= C) { ans = i, Parity = 0; break;}
else { C -= dF[N][i];}
}
vis[ans] = 1;
printf("%d ", ans);
for (int i = 2; i <= N; ++i) {
Parity ^= 1, pos = 0;
for (int j = 1; j <= N; ++j) {
if (vis[j]) continue;
++pos;
if ((!Parity && j<ans)||(Parity && j>ans)) {
if (Parity == 1) {
if (uF[N-i+1][pos] >= C) { ans = j; break;}
else { C -= uF[N-i+1][pos];}
} else {
if (dF[N-i+1][pos] >= C) { ans = j; break;}
else { C -= dF[N-i+1][pos];}
}
}
}
vis[ans] = 1;
printf("%d ", ans);
}
putchar('\n');
}
int main() {
init();
int K;
scanf("%d", &K);
while (K--) {
int N;
long long C;
scanf("%d%lld", &N, &C);
work(N, C);
}
return 0;
}
写计数的时候思路还是理不清哇..