Codeforces Round 887 (Div. 1)

发布时间 2023-08-02 21:36:27作者: 空気力学の詩

Preface

补一篇好久之前打的CF的博客,说实话其实B题我当时怎么想的已经忘的七七八八了

这场第一眼看A没啥思路,就先去把B这个构造写了,中间想了挺久的大概40min才写出来

然后回头一看A发现可以倒着做,就是个丁真题了然后15min写完

后面看C刚开始以为是个贪心,后面又感觉是个DP,但状态数比我预计的多,搞来搞去也没写出来

后面看Tutorial才发现原来是带悔贪心,捏麻麻的好像自从ZJOI2020那道题之后就再也没见到过这个算法的题了,苦路西


A. Ntarsis' Set

正着想不太好搞(其实也能做),安利一手徐神的正序的\(O(n)\)做法——CF1852A 中的结论证明

其实不妨倒着考虑问题,我们不妨将进行了\(i\)次删数操作的集合称为版本\(i+1\),则我们现在要求的是版本\(k+1\)中第一小的元素

那么倒着想这个元素其实就是版本\(k\)中第一个没有出现的元素,我们记此时它的位次为\(rk\)

那么再往前推一步这个元素其实就是版本\(k-1\)中第\(rk\)个没有出现的元素,不难发现可以一路推回到版本\(1\)中第几个没有出现的元素

具体实现可以直接用一个指针来单调移动,但比赛的时候我降智了写了个二分,不过无伤大雅

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair <int,int> pi;
const int N=200005;
int t,n,k,a[N],cnt,num[N],l[N],r[N],pfx[N];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld%lld",&n,&k),cnt=0,i=1;i<=n;++i)
		if (scanf("%lld",&a[i]),a[i]>a[i-1]+1)
		num[++cnt]=a[i]-a[i-1]-1,l[cnt]=a[i-1]+1,r[cnt]=a[i]-1;
		for (i=1;i<=cnt;++i) pfx[i]=pfx[i-1]+num[i];
		int ret=1; for (i=1;i<=k;++i)
		{
			int pos=lower_bound(pfx+1,pfx+cnt+1,ret)-pfx;
			if (pos>cnt) { ret=a[n]+ret-pfx[cnt]; continue; }
			ret=l[pos]+(ret-pfx[pos-1]-1);
		}
		printf("%lld\n",ret);
	}
	return 0;
}

B. Imbalanced Arrays

原谅我这题真忘了我当时怎么想的了,就说一个大概的印象吧

首先我们将\(a_i\)从大到小排序,同时我们设数组\(b_i\)表示对于每一个\(a_i\),我们都对\(b_1\sim b_{a_i}\)进行区间加\(1\)操作

则若\(\exist i\in[1,n],a_i\ne b_i\)则一定无解,不难发现这是充要条件

否则我们可以找到一个分界点\(pos\in[1,n]\),满足\([1,pos]\)中所有数最后取值都是正的,而\([pos+1,n]\)中所有数最后取值都是负的

由于最后数的值域是\([-n,n]\),我们不妨从\(n\)\(1\)枚举每个数,然后考虑每次要么将正的这个数放在前面部分,要么将负的这个数放在后面部分

具体放在哪边的话可以把贡献的式子写出来和目标的式子对比一下,不难发现这样一定可以构造出一组合法的解

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair <int,int> pi;
const int N=200005;
int T,n,a[N],b[N],ans[N]; pi t[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&T);T;--T)
	{
		RI i,j,k; for (scanf("%d",&n),i=0;i<=n;++i) b[i]=0;
		for (i=1;i<=n;++i) scanf("%d",&a[i]),++b[1],--b[a[i]+1],t[i]=pi(a[i],i);
		for (i=1;i<=n;++i) b[i]+=b[i-1]; bool flag=1;
		for (sort(t+1,t+n+1,greater<pi>()),i=1;i<=n&&flag;++i) if (t[i].fi!=b[i]) flag=0;
		if (!flag) { puts("NO"); continue; }
		int pos=n; for (i=1;i<=n;++i) if (b[i]<=i-1) { pos=i; break; }
		for (puts("YES"),i=1,j=n,k=n;k>=1;--k)
		if (i+(k-1)==b[i]) ans[t[i++].se]=k; else ans[t[j--].se]=-k;
		for (i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	}
	return 0;
}

C. Ina of the Mountain

首先考虑以下经典问题,给定数列\(\{a_n\}\),每次可以选择将一个区间内的所有数减去\(1\),求最后最少的操作次数使得所有数为\(0\)

根据差分我们容易知道最后的答案其实就是\(\sum_{i=1}^n \max(0,a_i-a_{i-1})\),但这道题相当于是可以将每个数加上若干个\(k\),求上述式子的最小值

考虑以下贪心的想法,首先不妨设\(a_i\)\(a_{i-1}\)加上的\(k\)的数量相同,则此时:

  • \(a_i<a_{i-1}\),则上述式子的贡献为\(0\),贪心地认为不动是最优的
  • \(a_i\ge a_{i-1}\),则有两种情况:
    • 不做其它修改,此时对答案的贡献为\(a_i-a_{i-1}\)
    • \(t\in[j,i)\)的所有\(a_t\)都加上\(k\),不难发现此时对答案的贡献为\(a_j-a_{j-1}+k\)

由于我们每次要求出当前的最优代价,因此考虑将对\([j,i)\)操作的这种方案视为反悔操作,用一个小根堆来维护所有反悔操作的最小贡献即可

要注意的是若我们选择在\(i\)这个时候反悔选择对\([j,i)\)进行操作,那么后续反悔选择在\(i\)这个位置时要额外付出的代价其实是\(a_i-a_{i-1}\)而非\(a_i-a_{i-1}+k\),因为\(a_{i-1}\)的值已经比原来的大\(k\)

剩下的细节看代码吧,复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<limits.h>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
const int N=200005;
int t,n,k,a[N]; LL ans;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		priority_queue <int,vector <int>,greater <int>> hp;
		RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
		for (ans=0,i=1;i<=n;++i)
		{
			int now=a[i]%k,pre=a[i-1]%k;
			if (now<pre) hp.push(now+k-pre); else
			hp.push(now-pre),ans+=hp.top(),hp.pop();
		}
		printf("%lld\n",ans);
	}
	return 0;
}

Postscript

如果能像这样一点一点地蹭上2100就好了……