【CF】1244G-Running in Pairs 题解

传送门:1244G
标签:构造

题目大意

贝拉托夫队由2n名选手组成,他们被分为两个跑道;每个跑道上有n名选手。每个跑道上的选手编号从1到n。编号为i的选手需要i秒跑完整条跑道。比赛按以下方式进行:首先,两个跑道上的选手同时起跑;当速度慢的人跑到终点后,第二个选手开始跑,所有人都等待直到速度慢的人完成跑步,以此类推,直到所有n对选手都跑完全程。组织者希望比赛尽可能长,但如果持续时间超过k秒,观众就会感到无聊。作为团队教练,你可以选择每个跑道上的选手排列顺序(但不能更改每个跑道上的选手数量或交换不同跑道之间的选手)。
你需要选择每个跑道上的选手顺序,以便比赛的持续时间尽可能长,但不超过k秒。正式地说,你需要找到两个排列p和q(均包含n个元素),使得sum = max(p_i,q_i)的最大可能值不超过k。如果没有这样的对,请说明这一点。

输入:第一行包含两个整数n和k(1≤n≤106,1≤k≤n2)——每个跑道上的选手数量和比赛的最大可能持续时间。

输出:如果无法重新安排选手以确保比赛持续时间不超过k秒,则输出-1。否则,输出三行。第一行应包含一个整数sum——比赛的最长可能持续时间不超过k。第二行应包含一个排列p_1,p_2,…,p_n(1≤p_i≤n,所有p_i应互异)——第一个跑道上参加比赛的选手编号。第三行应包含一个排列q_1,q_2,…,q_n(1≤q_i≤n,所有q_i应互异)——第二个跑道上参加比赛的选手编号。sum = max(p_i,q_i)的值应该是最大可能的,但不应超过k。如果有多个答案,请输出任何一种。

算法分析

  • 首先我们要理解哪些sum的值是可以获得的。显然,sum的最小可能值是mn = (n(n + 1)) / 2。sum的最大可能值是mx = ([n/2] + 1 + n) * [n/2] + n%2 * [n/2]。我们可以获得介于mn和mx之间的一切可能的sum值,我们将展示如何做到这一点。如果k < mn,则答案为-1(这是唯一这样的情况)。否则,答案存在,我们需要以某种方式构造它。
  • 假设第一个排列是同构排列(1, 2, …, n),并且我们只改变第二个排列。最初,第二个排列也是同构的。现在我们有sum=mn,我们需要将其改为sum=k或至多可能的数字不超过k。为了做到这一点,让我们学习如何增加sum。让我们看看如果交换n和n - 1会发生什么。那么sum的值将增加1。如果我们交换n和n - 2,那么sum的值将增加2,依此类推。如果我们交换n和1,那么sum的值将增加n - 1。
  • 所以,具体的算法实现如下:我们携带当前可以更改的排列段[l; r](一定存在,因为在某些交换之后,一些左端和右端的元素不能再增加sum的答案,因为它们已经被放置得最优了)以及我们要添加到sum中的值add,以获得最大的可能sum不超过k。初始l = 1, r = n, add = k - mn。现在让我们了解可以增加sum的最大值。现在它是r - l。如果这个值大于add,那么让我们交换pr和pr-add,打破循环(p是第二个排列)。否则,让我们交换pl和pr,减少add为r - l,并设置l := l + 1, r := r - 1。如果在某一时刻l变得大于或等于r,则打破循环。现在我们得到了第二个排列p,其sum的值最大且不超过k,我们可以计算数值答案(或打印min(mx, k)), 输出同构排列和排列p。

代码实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k;
const int maxn = 1e6+10;
int a[maxn],b[maxn];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>k;
	long long nm = (n+1)*n/2;
	if(k<nm) {cout<<-1; return 0;}
	long long ans=nm;
	for(int i=1;i<=n;i++) a[i]=i;
	int L=1,R=n;
	while(L<=R && ans<k) {
		long long add = a[R]-a[L];
		while(L<=R && ans+add>k) {
			L++;
			add = a[R]-a[L];
		}
		swap(a[L],a[R]);
		ans += add;
		L++; R--;
	}
	cout<<ans<<'\n';
	for(int i=1;i<=n;i++) cout<<i<<" ";cout<<'\n';
	for(int i=1;i<=n;i++) cout<<a[i]<<" ";cout<<'\n';
	return 0;
}



  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值