[POJ1037] [CEOI2002] A decorative fence [计数][dp]

探讨了如何求解字典序下第k个满足特定条件的排列问题,通过引入偏序关系,设计关联状态uF和dF,分别表示满足特定上升或下降条件的方案数,最终实现了高效的算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[ L i n k \frak{Link} Link]


题意:求按字典序从小到大、第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;
}

写计数的时候思路还是理不清哇..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值