P7690 [CEOI2002] A decorative fence(dp, 排列大小),dp4

//https://www.luogu.com.cn/problem/P7690
#include<bits/stdc++.h>
#include<unordered_map>
#include<array>
#define ll long long
#define ull unsigned long long
#define all(a) a.begin(),a.end()
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const int N = 2e5 + 5;

ll n, m, f[25][25][2], vis[25];
/*
f[i][j][k] : 总共i个数, 排列后第一数是第j大, 且第一位数的状态为k
k = 0, 第一个数小于第二个数
k = 1, 第一个数大于第二个数
n个数字符合要求的排列总数为f[n][1 ~ n][0 ~ 1]的和
*/
void init()
{
	fill(vis, vis + n + 1, 0);
	memset(f, 0, sizeof f);
	f[1][1][0] = f[1][1][1] = 1;
	for (int i = 2; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			for (int k = 1; k < i; k++)//加入一个数后j作为第一个数字的排列方式为i - 1个数字时比大于等于j的数字都加一, 其余不变
			{
				if (k < j)
					f[i][j][1] += f[i - 1][k][0];//第一个数为高位, 第二个数字选择比第一个数字小的
				else
					f[i][j][0] += f[i - 1][k][1];//第一个数为低位, 第二个数字选择比第一个数字大的
			}
		}
	}
}

void solve()//试填法, 从左到右依次确定每一位的数字
{
	cin >> n >> m;
	init();
	vector<int> ans;
	int last, k;
	for (int i = 1; i <= n; i++)//无法确定第一个数字是高危还是低位, 需要讨论
	{
		if (f[n][i][1] >= m)//因为按字典序排列, 当前位相同, 则当前位是高位使得下一位数字较小, 所以先讨论高位
		{
			last = i;
			k = 1;
			break;
		}
		else m -= f[n][i][1];
		if (f[n][i][0] >= m)
		{
			last = i;
			k = 0;
			break;
		}
		else
			m -= f[n][i][0];
	}
	vis[last] = 1;
	ans.emplace_back(last);
	
	for (int i = 2; i <= n; i++)
	{
		if (k == 1)
		{
			for (int j = 1; j < last; j++)
			{
				if (vis[j])
					continue;
				int rk = 1;
				for (int p = 1; p < j; p++)//计算j在剩下的数字中是第几大, 因为f[1][][]统计的是数字范围为[1, i]的方案数
					if (!vis[p])
						rk++;
				if (f[n - i + 1][rk][0] >= m)
				{
					last = j;
					k = 0;
					vis[j] = 1;
					ans.push_back(j);
					break;
				}
				m -= f[n - i + 1][rk][0];
			}
		}
		else
		{
			for (int j = last + 1; j <= n; j++)
			{
				if (vis[j])
					continue;
				int rk = 1;
				for (int p = 1; p < j; p++)
					if (!vis[p])
						rk++;
				if (f[n - i + 1][rk][1] >= m)
				{
					last = j;
					k = 1;
					vis[j] = 1;
					ans.push_back(j);
					break;
				}
				m -= f[n - i + 1][rk][1];
			}
		}
	}
	for (auto it : ans)
		cout << it << ' ';
	cout << '\n'; 
}

signed main()
{
	IOS;
	int t = 1;
	cin >> t;
	while (t--)
		solve();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值