CF:Codeforces Round #792 (Div. 1 + Div. 2) A ~ E 题解

291 阅读7分钟

本文已参加「新人创作礼」活动,一起开启掘金创作之路。

这次带来的是 Codeforces Round #792 (Div. 1 + Div. 2) A ~ E 的简单题解。

A. Digit Minimization

l 题意: 现有一个不含 0 的数字。Alice、Bob 两人轮流进行博弈,每一轮博弈如下:

  • 首先,Alice 可以交换任意不同两个数位的数字。
  • 接下来,Bob 可以删除最后一个数位的数字。

当只剩最后一位数字时游戏结束,Alice 希望最后保留的数字尽可能小,那么最终保留的数字是多少

l 分析: 显而易见的,对于不同数位的数字只存在有三种不同情况:

  • 数字长度为1:游戏结束,答案为 n 本身。
  • 数字长度为2:由于总是 Alice 先走,故必须交换数字,其次 Bob 删除最后一位数字。那么最后的答案一定是原数字中的最后一位数字,即个位数字。
  • 数字长度大于等于3:在最后一轮前,Alice 每次总是将最小的数位放到除最后一位外的任意位置;在最后一轮时,将最小数位移至开头。显而易见的,最后的答案一定是原数字中最小的一位数。

l 代码:

#include<bits/stdc++.h>
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define pb push_back
#define pa pair<int,int>
#define mk make_pair
 
using namespace std;
 
inline ll read()
{
    ll t=0,dp=1;char c=getchar();
    while(!isdigit(c))  {if(c=='-') dp=-1;c=getchar();}
    while(isdigit(c))   t=t*10+c-48,c=getchar();
    return t*dp;
}
inline void write(ll x){if(x<0)  {putchar('-');x=-x;}    if(x>=10)    write(x/10);putchar(x%10+48);}
inline void writeln(ll x){write(x);puts("");}
inline void write_p(ll x){write(x);putchar(' ');}

int main()
{
	int t;
	string s;
	t=read();
	while(t--)
	{
		cin>>s;
		if(s.size()==2) {cout<<s[1]<<endl;continue;}
		char a='9';
		For(i,0,s.size()-1) a=min(a,s[i]);
		cout<<a<<endl;
	}
}

B. Z mod X = C

l 题意: 对于 [公式] 找出合适的 [公式] ,其中

[公式]

l 分析: 显而易见的,当 x=a+b+c,y=b+c,z=c 时,一定满足情况(大家可以自己证明一下)

l 代码:

#include<bits/stdc++.h>
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define pb push_back
#define pa pair<int,int>
#define mk make_pair
 
using namespace std;
 
inline ll read()
{
    ll t=0,dp=1;char c=getchar();
    while(!isdigit(c))  {if(c=='-') dp=-1;c=getchar();}
    while(isdigit(c))   t=t*10+c-48,c=getchar();
    return t*dp;
}
inline void write(ll x){if(x<0)  {putchar('-');x=-x;}    if(x>=10)    write(x/10);putchar(x%10+48);}
inline void writeln(ll x){write(x);puts("");}
inline void write_p(ll x){write(x);putchar(' ');}

int main()
{
	int t,a,b,c;
	t=read();
	while(t--)
	{
		a=read();
		b=read();
		c=read();
		write_p(a+b+c);
		write_p(b+c);
		writeln(c);
	}
}

C. Column Swapping (fst)

l 题意: 给定若干数组 [公式] ,每个数组恰有 m 个元素。

那么能否选择两个位置 [公式] ,对于每个数组 [公式] 交换 [公式] ,使得交换后,所有数组保持有序。

如果可以,输出这样的位置。否则输出 -1。

l 分析: 首先,让我们检查一下给定的表是否是好的。如果不是,那么有一行的元素应该被替换。

  • 假设这一行是 a,b 是排序后的 a 行。然后让我们找到使得 ai≠bi 的位置集 i。
  • 如果至少有 3 个这样的位置,那么答案是 -1,因为通过交换,我们最多可以删除 2 个这样的坏位置。
  • 如果这样的位置不超过 2 个,那么我们就交换相应的列并检查每一行是否被排序。
  • 如果该表是好的,我们就找到了答案。如果不是,那么答案是 -1,因为我们不能对 a 进行排序,之后得到一个好的表格。

l 注意: 不知道是什么原因,下面这段代码 fst 了,Wrong answer on test 13,但实际上,我单独将这个测试点复制出来单独运行,显示的答案是正确的,这很奇怪。

l 代码:

#include<bits/stdc++.h>
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define pb push_back
#define pa pair<int,int>
#define mk make_pair
 
using namespace std;
 
inline ll read()
{
    ll t=0,dp=1;char c=getchar();
    while(!isdigit(c))  {if(c=='-') dp=-1;c=getchar();}
    while(isdigit(c))   t=t*10+c-48,c=getchar();
    return t*dp;
}
inline void write(ll x){if(x<0)  {putchar('-');x=-x;}    if(x>=10)    write(x/10);putchar(x%10+48);}
inline void writeln(ll x){write(x);puts("");}
inline void write_p(ll x){write(x);putchar(' ');}
#define int long long
vector<int> v[222222],a[222222];
signed main()
{
	int t;
	t=read();
	while(t--)
	{
		int n,m,flag=0;
		n=read();
		m=read();
		For(i,0,n-1) v[i].resize(m);
		For(i,0,m-1) a[i].resize(n);
		For(i,0,n-1) For(j,0,m-1) v[i][j]=read();
		For(i,0,n-1) For(j,0,m-1) a[j][i]=v[i][j];
		sort(a,a+m);
		For(i,0,n-1 && !flag) 
			For(j,1,m-1 && !flag)
				if(a[j][i]<a[j-1][i])
				{
					puts("-1");
					flag=1;
				}
		if(flag) continue;
		pair<int,int> ans{-1,-1};
		For(i,0,n-1 && !flag)
			For(j,0,m-1 && !flag)
				if(a[j][i]!=v[i][j])
				{
					if(ans.first==-1) ans.first=j;
					else if(ans.second==-1) ans.second=j;
					else if(j!=ans.first && j!=ans.second)
					{
						puts("-1");
						flag=1;
					}
				}
		if(ans.first==-1 && ans.second==-1) puts("1 1");
		else {write_p(ans.first+1);writeln(ans.second+1);}
	}
}

D. Traps

题意: 现有 n 个陷阱,每个陷阱的代价是 [公式] ,你可以选择至多 k 个陷阱,跳过它们(不需要付出代价)。如果选择跳过该陷阱,虽然当前陷阱不需要代价,但其他陷阱的代价均加 1。那么至少需要多少代价,才能按顺序通过所有陷阱?

l 分析: 贪心准则是:选择以下的值较大的跳过:

  • 设下标是 [公式] ,代价是 [公式] ,优先跳过 [公式] 较大的陷阱。

该证明来源于 pzr 大佬。

设我们选择了 [公式] 的位置跳过,则最终答案可以表示为:

[公式]


l 代码:

#include<bits/stdc++.h>
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define pb push_back
#define pa pair<int,int>
#define mk make_pair
 
using namespace std;
 
inline ll read()
{
    ll t=0,dp=1;char c=getchar();
    while(!isdigit(c))  {if(c=='-') dp=-1;c=getchar();}
    while(isdigit(c))   t=t*10+c-48,c=getchar();
    return t*dp;
}
inline void write(ll x){if(x<0)  {putchar('-');x=-x;}    if(x>=10)    write(x/10);putchar(x%10+48);}
inline void writeln(ll x){write(x);puts("");}
inline void write_p(ll x){write(x);putchar(' ');}
int a[222222];
int main()
{
	int t;
	t=read();
	while(t--)
	{
		int n=read(),k=read();
		ll sum=0;
		For(i,1,n)
		{
			a[i]=read();
			sum+=a[i];
		}
		sum+=(ll)n*k;
		For(i,1,k) sum-=(k-i);
		For(i,1,n) a[i]+=i;
		sort(a+1,a+n+1,greater<int> ());
		For(i,1,k) sum-=a[i];
		writeln(sum);
	}
}

E. MEX vs DIFF

l 题意: 给定数组,改变至多 k 个元素,使得数组的 DIFF - MEX 最小。

  • MEX 是最小的没有出现在数组中的正整数元素。
  • DIFF 是数组中元素的个数(值相同的元素仅计数一次)。

l 分析: 考虑所有操作后可能的 MEX,按照递增的顺序检查它们。

  • 现在让我们固定一些 MEX=m。数组中应该有从 0 到 m 的所有数字,所以数组中有些 "洞" 应该被覆盖。洞是指从 0 到 m 的整数,它不存在于数组中。如果最后至少有一个洞,就不可能得到 MEX=m。

  • 现在让我们来看看我们应该如何覆盖这些洞。首先,我们需要使用大于 m 的整数。显而易见的,使用这些整数总是不比从 0 到 m 的整数差。这是因为每次我们覆盖一个洞时,MEX 至少增加一个(我们以增加的顺序覆盖洞),DIFF 的值最多增加 1,当我们改变最后一个相同的元素时,它不会增加。

  • 之后,如果我们使用了所有大于 m 的整数,我们应该使用那些从 0 到 m 的整数,但只使用那些出现一次以上的整数。

  • 通过这些操作,我们至少使 MEX 增加了 1,并使 DIFF 正好增加了 1(因为我们覆盖了一个洞)。

  • 现在我们注意到,当按照递增的顺序考虑每个 MEX 值时,我们可以简单地保持一些关于数组当前状态的信息:

    • 一个帮助我们找到大于m的元素在数组中出现次数较少的集合
    • 未覆盖的洞的数量
    • 从 0 到 m 的 "奖励" 元素的数量(从 0 到 m 的整数减去从 0 到 m 的那些元素的 DIFF),并且很容易看到当我们增加 MEX 时,它是如何改变的。
  • 所以总的来说,我们可以计算出从 0 到 n 的所有 MEX 的答案。

l 代码:

#include<bits/stdc++.h>
#define For(i,j,k)  for(int i=j;i<=k;++i)
#define Dow(i,j,k)  for(int i=k;i>=j;--i)
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define pb push_back
#define pa pair<int,int>
#define mk make_pair
 
using namespace std;
 
inline ll read()
{
    ll t=0,dp=1;char c=getchar();
    while(!isdigit(c))  {if(c=='-') dp=-1;c=getchar();}
    while(isdigit(c))   t=t*10+c-48,c=getchar();
    return t*dp;
}
inline void write(ll x){if(x<0)  {putchar('-');x=-x;}    if(x>=10)    write(x/10);putchar(x%10+48);}
inline void writeln(ll x){write(x);puts("");}
inline void write_p(ll x){write(x);putchar(' ');}
int a[222222],vis[222222];
int main()
{
	int t;
	t=read();
	while(t--)
	{
		int n=read(),k=read();
		map<int,int> mp;
		For(i,1,n)
		{
			int x=read();
			mp[x]++;
		}
		int cnt=k,mex=n;
		For(i,0,n-1)
			if(!mp[i])
			{
				if(cnt) cnt--;
				else
				{
					mex=i;
					break;
				}
			}
		set<pair<int,int>> s;
		for(auto [x,y]:mp) if(x>mex) s.insert({y,x});
		while(s.size() && k>=(s.begin()->first))
		{
			k-=s.begin()->first;
			s.erase(s.begin());
		}
		writeln(s.size());
	}
}

F~H

这真的好难,我根本不会诶。