You are god again!|

definieren

园龄:3年1个月粉丝:7关注:4

2023-11-12 15:57阅读: 28评论: 0推荐: 0

[题解] P9838 挑战 NPC IV

P9838 挑战 NPC IV

定义 f(x)=1+log2lowbit(x)
定义一个 1n 的排列 p 的权值是 l=1nr=lni[l,r]f(pi)
求所有 1n 的排列中权值第 k 小的排列的权值,对 998244353 取模。
n1018,kmin(n!,1018)

首先可以发现,权值最小的排列数量其实是很多的,大概有 i=0log2n(n2in2i+1)! 个。这个数在 n=29 时就超过了 1018,所以当 n>28 时,只需考虑求最小的权值;当 n28 时,可以直接暴力 dp。

Part1 n>28

不难发现,权值实际上就是 i=1nf(pi)i(ni+1)

这里有一个贪心就是 f(pi) 大的数尽量往两边放,小的尽量往中间放,这个是显然的。

然后去优化这个贪心。考虑先求出每种 f(x) 的数量,然后一种一种的放。数量是好求的,然后需要求的就是:

i[l,r]i(ni+1)=(n+1)(r(r+1)2(l1)l2)+(r(r+1)(2r+1)6+(l1)l(2l1)6)

由于本质不同的 f(x) 的数量是 O(logn) 的,所以这部分时间复杂度是 O(logn) 的。

Part2 n28

这部分大体的思路就是由于最大的权值是 6000 左右的,所以可以对每种权值求出它对应的排列的个数。

由于本质不同的 f(x) 至多有 5 个,所以可以设计 dp 为 fnow1,now2,now3,now4,now5,val 表示 f(x)=i 的选了 nowi 个,权值为 val 时的方案数,转移就是枚举哪个数多选了一个向后转移。

状态数大概是 15×8×5×3×2×5983=21538800

constexpr int MAXLG = 60, inv2 = 499122177, inv6 = 166374059;
ll n, k;

namespace Solve1 {
	ll lim[MAXLG];
	
	int calc(ll L, ll R) {
		if (L > R) return 0;
		auto s1 = [&](int n) { return mul(mul(n, n + 1), inv2); };
		auto s2 = [&](int n) { return mul(mul(mul(n, n + 1), 2 * n + 1), inv6); };
		auto sum1 = [&](int l, int r) { return del(s1(r), s1(l - 1)); };
		auto sum2 = [&](int l, int r) { return del(s2(r), s2(l - 1)); };
		int N = (n + 1) % MOD, l = L % MOD, r = R % MOD;
		int ans = del(mul((int)((n + 1) % MOD), sum1(l, r)), sum2(l, r));
		return add(ans, ans);
	}
	
	int Main() {
		for (int i = 0; i <= __lg(n); i ++) lim[i] = (n >> i) - (n >> i + 1);
		int ans = 0; ll pos = 1; bool lst = false;
		for (int i = __lg(n); ~i; i --) {
			int ret = 0; ll cnt = lim[i];
			if (lst) {
				cadd(ret, (int)mul(pos % MOD, (n - pos + 1) % MOD));
				cnt --, pos ++, lst = false;
			}
			if (cnt > 1) {
				cadd(ret, calc(pos, pos + (cnt >> 1) - 1));
				pos += cnt >> 1, cnt &= 1;
			}
			if (cnt) cadd(ret, (int)mul(pos % MOD, (n - pos + 1) % MOD)), lst = true;
			cadd(ans, mul(ret, i + 1));
		}
		Write(ans, '\n');
		return 0;
	}
}

namespace Solve2 {
	int cnt[6], val[30];
	ll f[15][8][5][3][2][5983];
	
	int Main() {
		for (int i = 1; i <= n; i ++)
			cnt[__lg(i & -i) + 1] ++;
		ll ret = 1;
		for (int i = 1; i <= __lg(n) + 1; i ++)
			for (int j = 1; j <= cnt[i]; j ++) ret *= j;
		for (int i = 1; i <= n; i ++)
			val[i] = i * (n - i + 1);
		sort(val + 1, val + n + 1);
		{
			int now[6];
			f[0][0][0][0][0][0] = 1;
			for (now[1] = 0; now[1] <= cnt[1]; now[1] ++) for (now[2] = 0; now[2] <= cnt[2]; now[2] ++)
			for (now[3] = 0; now[3] <= cnt[3]; now[3] ++) for (now[4] = 0; now[4] <= cnt[4]; now[4] ++)
			for (now[5] = 0; now[5] <= cnt[5]; now[5] ++) {
				int sum = 1;
				for (int i = 1; i <= 5; i ++) sum += now[i];
				for (int u = 0; u <= 5982; u ++) {
					for (int i = 1; i <= 5; i ++) if (now[i] < cnt[i]) {
						ll pre = f[now[1]][now[2]][now[3]][now[4]][now[5]][u];
						now[i] ++; int v = u + i * val[sum];
						if (v > 5982) { now[i] --; continue; }
						f[now[1]][now[2]][now[3]][now[4]][now[5]][v] += pre;
						now[i] --;
					}
				}
			}
		}
		for (int i = 0; i <= 5982; i ++) {
			k -= f[cnt[1]][cnt[2]][cnt[3]][cnt[4]][cnt[5]][i] * ret;
			if (k <= 0) { Write(i, '\n'); break; }
		}
		{
			for (int a = 0; a <= cnt[1]; a ++) for (int b = 0; b <= cnt[2]; b ++)
			for (int c = 0; c <= cnt[3]; c ++) for (int d = 0; d <= cnt[4]; d ++)
			for (int e = 0; e <= cnt[5]; e ++) for (int i = 0; i <= 5982; i ++)
				f[a][b][c][d][e][i] = 0;
			for (int i = 1; i <= __lg(n) + 1; i ++) cnt[i] = 0;
		}
		return 0;
	}
}

void slv() {
	n = Read<ll>(), k = Read<ll>();
	if (n > 28) return Solve1::Main(), void();
	else return Solve2::Main(), void();
}
posted @   definieren  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起