题解:P11277 世界沉睡童话

比较简单的构造。

注意到题面给出 \(a_i\le 2n-1\) 的条件,考虑这个有什么用,你会发现从 \(n\)\(2n-1\)\(n\) 个数都是两两互不为约数,所以当我们构造出序列后,这些数可以用来填补空位。

\(k\) 的上界是 \(\frac{n(n-1)}{2}\),显然在全部都为同一个数的时候取到,显然有 \(x\) 个数相同的时候,这 \(x\) 个数至少产生 \(\frac{x(x-1)}{2}\) 的贡献,那我们是否可以利用这个性质来构造出一个合法序列呢?当然是可以的。

首先给出这样一个性质,当我们取到的数为 \(y\) 的时候,只由一些 \(y\) 和一些 \(dy\)\(d\) 为常数)组成的可重集 \(S\),和只由一些 \(y\) 组成的可重集 \(T\),若 \(|S|=|T|\),那么这两个产生的贡献是相等的,可以通过找规律或者灵光一现知道这件事。证明的话就是两种集合内部都是任意两种元素都可以自由握手的,所以方案数在集合大小相等时显然相等。

我们接着想,自由握手是相等的,那么再加入一个不能自由握手的元素呢?以 \(y,2y,3y\) 为例子,分别有 \(x_1,x_2\)\(y,2y\),总和为 \(x\),如果加入 \(1\)\(3y\),最终产生的贡献就会是 \(\frac{x(x-1)}{2}+x_1\),总和不变的情况下,将 \(x_1\) 取遍 \([0,n)\) 的话我们可以做到贡献取遍 \([\frac{x(x-1)}{2},\frac{(x+1)x}{2})\),那么由不同的 \(x\) 以及 \(x_1\) 取值,以及是否加入 \(3y\),贡献就可以取遍 \([0,\frac{n(n-1)}{2}]\),空位直接由 \(n\)\(2n-1\) 中没被取的填上。

具体地就是找到最大的满足 \(\frac{x(x-1)}{2}\le k\)\(x\),然后按上述构造法构造。

为了不产生额外的贡献,我们最优的取法显然是取 \(\lfloor\frac{2n-1}{3}\rfloor\) 作为 \(y\),这样是它倍数的数一般只有 \(2\) 个,可以做到与其他数不产生贡献。为什么是一般,因为显然在一种最特殊的情况下 \(n=3\) 时,\(y\) 取到 \(1\),这时就不成立,要特判一下。

实现有点细节,但不多。

#include <bits/stdc++.h>
#include <bits/extc++.h>
#define ll long long
#define ull unsigned long long
#define m_p make_pair
#define m_t make_tuple
#define N 250010
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using namespace __gnu_pbds;
signed main()
{
	// auto __IN__ = freopen(".in", "r", stdin);
	// auto __OUT__ = freopen(".out", "w", stdout);
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	ll n, k, val, l, r, mid, ans = 0, d, p;
	cin >> n >> k;
	if(n==3&&k==2){
		cout << "1 2 3";
		return 0;
	}
	l = 0;
	r = n;
	while (l <= r)
	{
		mid = l + r >> 1;
		if (mid * (mid - 1) / 2ll <= k)
		{
			ans = mid;
			l = mid + 1;
		}
		else
			r = mid - 1;
	}
	val = (n * 2 - 1) / 3;
	if (val == 1 && n == 3)
		++val;
	d = k - ans * (ans - 1) / 2ll;
	// cerr << d << "\n";
	if (ans < 2)
		ans = 0;
	for (int i = 1; i <= d; i++)
		cout << val << " ";
	for (int i = 1; i <= ans - d; i++)
		cout << val * 2 << " ";
	p = n;
	if (ans < n && ans && ans * (ans - 1) / 2ll != k)
	{
		--n;
		cout << val * 3 << " ";
	}
	for (int i = ans + 1; i <= n; i++)
	{
		if (k && (p == val * 3 || p == val * 2 || p == val))
			++p;
		cout << p << " ";
		++p;
	}
	return 0;
}
posted @ 2024-11-15 08:58  -wryyy-  阅读(10)  评论(0编辑  收藏  举报