AtCoder Beginner Contest 260 A~F 题解

ABC260 A~F

  • [A - A Unique Letter](https://atcoder.jp/contests/abc260/tasks/abc260_a)
    • 题目大意
    • 输入格式
    • 输出格式
    • 样例
    • 分析
    • 代码
  • [B - Better Students Are Needed!](https://atcoder.jp/contests/abc260/tasks/abc260_b)
    • 题目大意
    • 输入格式
    • 输出格式
    • 样例
    • 分析
    • 代码
      • 代码1
      • 代码2
  • [C - Changing Jewels](https://atcoder.jp/contests/abc260/tasks/abc260_c)
    • 题目大意
    • 输入格式
    • 输出格式
    • 样例
    • 分析
    • 代码
  • [D - Draw Your Cards](https://atcoder.jp/contests/abc260/tasks/abc260_d)
    • 题目大意
    • 输入格式
    • 输出格式
    • 样例
    • 分析
    • 代码
  • [E - At Least One](https://atcoder.jp/contests/abc260/tasks/abc260_e)
    • 题目大意
    • 输入格式
    • 输出格式
    • 样例
    • 分析
    • 代码
  • [F - Find 4-cycle](https://atcoder.jp/contests/abc260/tasks/abc260_f)
    • 题目大意
    • 输入格式
    • 输出格式
    • 样例
    • 分析
    • 代码

A - A Unique Letter

题目大意

给定一个长度为 3 3 3的字符串 S S S
输出 S S S中出现正好一次的字母(任意,如abc中,三个字母都可为答案)。

如果没有,输出-1

数据保证 S S S的长为 3 3 3,且由小写英文字母组成。

输入格式

S S S

输出格式

输出任意符合条件的答案。

样例

S S S 输出
pop o
abc a/b/c
xxx -1

分析

我们设输入的3个字母分别为abc
首先,如果 a = b = c a=b=c a=b=c,那么输出 − 1 -1 1
其次,我们依次尝试找到两个相同的字母:

  • xxy形式( a = b a=b a=b):输出 c c c
  • xyx形式( a = c a=c a=c):输出 b b b
  • yxx形式( b = c b=c b=c):输出 a a a
  • xyz形式( a ≠ b ≠ c a\ne b\ne c a=b=c):输出任意一个

代码

这里,我把最后两种情况合并了(一个else搞定,都输出 a a a):

#include 
using namespace std;

int main()
{
	char a = getchar(), b = getchar(), c = getchar();
	if(a == b && b == c) puts("-1");
	else if(a == c) putchar(b);
	else if(a == b) putchar(c);
	else putchar(a);
	return 0;
}

B - Better Students Are Needed!

题目大意

N N N个员工参加了一场选聘考试。
i i i个员工数学考了 A i A_i Ai分,英语 B i B_i Bi分。

公司按如下的方式选聘员工:

  1. 数学分数在前 X X X的被直接录取;
  2. 剩下的人中,英语分数在前 Y Y Y的被录取;
  3. 最后,总分在前 Z Z Z的被录取,剩下的人被淘汰。

注意:分数相同的员工按编号排序。

输出被录取的所有员工的编号,按升序排列。

1 ≤ N ≤ 1000 1\le N\le 1000 1N1000
0 ≤ X , Y , Z ≤ N 0\le X,Y,Z\le N 0X,Y,ZN
1 ≤ X + Y + Z ≤ N 1\le X+Y+Z\le N 1X+Y+ZN
0 ≤ A i , B i ≤ 100 0\le A_i,B_i\le 100 0Ai,Bi100

输入格式

N   X   Y   Z N~X~Y~Z N X Y Z
A 1   A 2   …   A N A_1~A_2~\dots~A_N A1 A2  AN
B 1   B 2   …   B N B_1~B_2~\dots~B_N B1 B2  BN

输出格式

输出被录取的所有员工的编号,按升序排列,每行一个。

样例

略,请自行前往AtCoder查看

分析

本题主要有两种思路:

  1. pair代表一个员工,再使用vector+sortpriority_queue执行三次分别排序数学、英语、总分;
  2. struct { int math, english, id; }表示员工,存储一次,排序三次(使用不同的排序依据)

详见代码1、代码2。

代码

代码1

  • vector+sort实现
    #include 
    #include 
    #include 
    #define maxn 1005
    using namespace std;
    
    int a[maxn], b[maxn];
    bool used[maxn];
    
    int main()
    {
    	int n, x, y, z;
    	scanf("%d%d%d%d", &n, &x, &y, &z);
    	for(int i=0; i<n; i++)
    		scanf("%d", a + i);
    	for(int i=0; i<n; i++)
    		scanf("%d", b + i);
    	
    	// Math
    	vector<pair<int, int>> sel_a;
    	for(int i=0; i<n; i++)
    		sel_a.emplace_back(-a[i], i);
    	sort(sel_a.begin(), sel_a.end());
    	for(int i=0; i<x; i++)
    		used[sel_a[i].second] = true;
    	
    	// English
    	vector<pair<int, int>> sel_b;
    	for(int i=0; i<n; i++)
    		if(!used[i])
    			sel_b.emplace_back(-b[i], i);
    	sort(sel_b.begin(), sel_b.end());
    	for(int i=0; i<y; i++)
    		used[sel_b[i].second] = true;
    	
    	// Total
    	vector<pair<int, int>> sel_t;
    	for(int i=0; i<n; i++)
    		if(!used[i])
    			sel_t.emplace_back(-(a[i] + b[i]), i);
    	sort(sel_t.begin(), sel_t.end());
    	for(int i=0; i<z; i++)
    		used[sel_t[i].second] = true;
    	
    	for(int i=0; i<n; i++)
    		if(used[i])
    			printf("%d\n", i + 1);
    	return 0;
    }
    
  • priority_queue实现
    #include 
    #include 
    #define maxn 1005
    using namespace std;
    
    int a[maxn], b[maxn], c[maxn];
    bool used[maxn];
    
    inline void selectOnce(int* scores, int n, int snum)
    {
    	priority_queue<pair<int, int>> sel;
    	for(int i=0; i<n; i++)
    		if(!used[i])
    		{
    			sel.emplace(-scores[i], i);
    			if(sel.size() > snum) sel.pop();
    		}
    	while(!sel.empty())
    		used[sel.top().second] = true, sel.pop();
    }
    
    int main()
    {
    	int n, x, y, z;
    	scanf("%d%d%d%d", &n, &x, &y, &z);
    	for(int i=0; i<n; i++)
    		scanf("%d", a + i);
    	for(int i=0; i<n; i++)
    		scanf("%d", b + i);
    	for(int i=0; i<n; i++)
    		c[i] = a[i] + b[i];
    	selectOnce(a, n, x);
    	selectOnce(b, n, y);
    	selectOnce(c, n, z);
    	for(int i=0; i<n; i++)
    		if(used[i])
    			printf("%d\n", i + 1);
    	return 0;
    }
    

代码2

#include 
#include 
#include 
#define maxn 1005
using namespace std;

struct Emp { // Employee
	int math, eng, id;
} emps[maxn];

inline bool cmp1(const Emp& e1, const Emp& e2) {
	return e1.math == e2.math?
			e1.id < e2.id:
			e1.math > e2.math;
}

inline bool cmp2(const Emp& e1, const Emp& e2) {
	return e1.eng == e2.eng?
			e1.id < e2.id:
			e1.eng > e2.eng;
}

inline bool cmp3(const Emp& e1, const Emp& e2) {
	int tot1 = e1.math + e1.eng, tot2 = e2.eng + e2.math;
	return tot1 == tot2?
			e1.id < e2.id:
			tot1 > tot2;
}

inline bool cmp4(const Emp& e1, const Emp& e2) {
	return e1.id < e2.id;
}

int main()
{
	// Input
	int n, x, y, z;
	scanf("%d%d%d%d", &n, &x, &y, &z);
	
	for(int i=0; i<n; i++)
		scanf("%d", &emps[i].math),
		emps[i].id = i;
	for(int i=0; i<n; i++)
		scanf("%d", &emps[i].eng);
	
	// Sort
	auto last = emps + n;
	sort(emps, last, cmp1);
	sort(emps + x, last, cmp2);
	sort(emps + x + y, last, cmp3);
	sort(emps, emps + x + y + z, cmp4); // 按编号升序排序
	
	// Output
	for(int i=0; i<x+y+z; i++)
		printf("%d\n", emps[i].id + 1);
	return 0;
}

C - Changing Jewels

题目大意

Takahashi有一个 N N N级的红色宝石。
他可以重复下列操作任意次数:

  • 将一个 N N N级的红色宝石转换为“一个 ( N − 1 ) (N-1) (N1)级的红色宝石和 X X X N N N级的蓝色宝石”。
  • 将一个 N N N级的蓝色宝石转换为“一个 ( N − 1 ) (N-1) (N1)级的红色宝石和 Y Y Y N − 1 N-1 N1级的蓝色宝石”。

Takahashi最后最多能得到几个 1 1 1级的蓝色宝石?

1 ≤ N ≤ 10 1\le N\le 10 1N10
1 ≤ X , Y ≤ 5 1\le X,Y\le 5 1X,Y5

输入格式

N   X   Y N~X~Y N X Y

输出格式

输出一个整数,即最终蓝色宝石的数量。

样例

N N N X X X Y Y Y 输出
2 2 2 3 3 3 4 4 4 12 12 12
10 10 10 5 5 5 5 5 5 3942349900 3942349900 3942349900

注意小心 32 32 32位整数(int/int32)溢出。

分析

要获得 ( N − 1 ) (N-1) (N1)级的蓝宝石,必须先尽可能多的获得 N N N级的蓝宝石。
而要达到这个目的,就需要有尽可能多的 N N N级红宝石。

以此类推,我们可以按顺序进行操作 1 1 1,操作 2 2 2……直到所有宝石全部为 1 1 1级(也就是循环 ( N − 1 ) (N-1) (N1)次)。维护两个变量 red \text{red} red(初始为 1 1 1)和 blue \text{blue} blue(初始为 0 0 0),分别表示当前的红、蓝宝石的数目。
每次循环,先将 blue \text{blue} blue加上 red × X \text{red}\times X red×X(操作 1 1 1),再将 red \text{red} red加上 blue \text{blue} blue blue \text{blue} blue乘上 Y Y Y(操作 2 2 2)。

时间复杂度 O ( n ) \mathcal O(n) O(n),如有读不懂的地方,可参考代码。

代码

注意使用long long

#include 
using namespace std;

int main()
{
	int n, x, y;
	scanf("%d%d%d", &n, &x, &y);
	long long red = 1LL, blue = 0LL;
	while(--n)
	{
		blue += red * x;
		red += blue, blue *= y;
	}
	printf("%lld\n", blue);
	return 0;
}

D - Draw Your Cards

题目大意

N N N张牌,上面分别写着数字 P 1 , P 2 , … , P N P_1,P_2,\dots,P_N P1,P2,,PN
按照这个顺序,我们进行 N N N个操作,第 i i i个操作的具体步骤如下:

  • 取出第 i i i张牌,令 X = P i X=P_i X=Pi
  • 找到存堆中顶牌   ≥ X ~\ge X  X的最小一张,将这张牌置于其上;
  • 如果没有符合条件的牌,将 X X X放入一新堆;
  • 当某堆牌数达到 K K K时,把这堆的牌全部吃掉。

求每张牌被吃掉的时间(若没有被吃掉,输出-1,详见输出格式)。

1 ≤ K ≤ N ≤ 2 × 1 0 5 1\le K\le N \le 2\times 10^5 1KN2×105
P P P ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,,N)的一种排列。

输入格式

N   K N~K N K
P 1   P 2   …   P N P_1~P_2~\dots~P_N P1 P2  PN

输出格式

输出 N N N行,第 i i i行表示卡片 i i i被吃掉的时间(如果没被吃掉,输出-1)。

样例

略,就是懒

分析

首先肯定不能用vector>这种数据结构,效率太低,容易写错,还不好用。可以用一个类似于并查集的数据结构,每次叠放操作都可看作“把下面的牌的父亲设置为上面的牌”。我们还需要记录并查集中每个连通分量的大小,方便模拟“吃掉”操作。

最终对于每个节点,输出其祖宗被吃掉的时间(咋听起来有点怪)。

目前的时间复杂度是 O ( N 2 ) \mathcal O(N^2) O(N2),因为每次操作都需要用 O ( n ) \mathcal O(n) O(n)的时间,找到最小的符合条件的牌堆。
很容易想到,可以使用set优化。

set是自动排序的集合,常用的的操作有插入(insert)、删除(erase)、二分查找(lower_bound/upper_bound),一次操作的时间复杂度均为 O ( log ⁡ n ) \mathcal O(\log n) O(logn)

这时,使用一个set维护每个堆顶的卡牌编号,就可以把时间复杂度降到 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)以内。

至此,此题完。注意对 K = 1 K=1 K=1的特判。

代码

#include 
#include 
#define maxn 200005
using namespace std;

int fa[maxn], eat[maxn], sz[maxn];

int find(int x) {
	return fa[x] == x? x: fa[x] = find(fa[x]);
}

int main()
{
	int n, k;
	scanf("%d%d", &n, &k);
	set<int> cards;
	for(int i=0; i<n; i++)
	{
		int x;
		scanf("%d", &x);
		x --;
		eat[x] = -1, fa[x] = x;
		if(k == 1)
		{
			eat[x] = i + 1;
			continue;
		}
		auto it = cards.upper_bound(x);
		if(it == cards.end())
			cards.insert(x), sz[x] = 1;
		else
		{
			fa[*it] = x;
			cards.erase(it);
			if((sz[x] = sz[*it] + 1) == k)
				eat[x] = i + 1;
			else cards.insert(x);
		}
	}
	for(int i=0; i<n; i++)
		printf("%d\n", eat[find(i)]);
	return 0;
}

E - At Least One

题目大意

给定整数 M M M N N N对整数: ( A 1 , B 1 ) , ( A 2 , B 2 ) , … , ( A N , B N ) (A_1,B_1),(A_2,B_2),\dots,(A_N,B_N) (A1,B1),(A2,B2),,(AN,BN)
题目保证对于任意 i i i 1 ≤ A i < B i ≤ M 1\le A_i1Ai<BiM

符合如下条件的整数序列 S S S被称作好的序列

  • S S S ( 1 , 2 , … , M ) (1,2,\dots,M) (1,2,,M)的连续子序列;
  • 对于每个 i i i S S S中包含 A i A_i Ai B i B_i Bi(或同时包含)。

f ( k ) = ( f(k)=( f(k)=(长为 k k k的好序列的个数 ) ) )。求 f ( 1 ) , f ( 2 ) , … , f ( M ) f(1),f(2),\dots,f(M) f(1),f(2),,f(M)

1 ≤ N ≤ 2 × 1 0 5 1\le N\le 2\times 10^5 1N2×105
2 ≤ M ≤ 2 × 1 0 5 2\le M\le 2\times 10^5 2M2×105
1 ≤ A i < B i ≤ M 1\le A_i1Ai<BiM

输入格式

N   M N~M N M
A 1   B 1 A_1~B_1 A1 B1
A 2   B 2 A_2~B_2 A2 B2
⋮ \vdots
A N   B N A_N~B_N AN BN

输出格式

输出一行,即 f ( 1 ) , f ( 2 ) , … , f ( M ) f(1),f(2),\dots,f(M) f(1),f(2),,f(M),用空格分隔。

样例

略,请自行前往AtCoder查看

分析

首先,根据题意, S S S可被表示为一个区间 [ l , r ] [l,r] [l,r],其中 1 ≤ l ≤ r ≤ M 1\le l\le r\le M 1lrM
当对于每个 i i i l ≤ A i ≤ r l\le A_i\le r lAir l ≤ B i ≤ r l\le B_i\le r lBir时,区间 [ l , r ] [l,r] [l,r]符合条件。
若按这样直接暴力枚举,时间复杂度为 O ( N 2 M ) \mathcal O(N^2M) O(N2M),明显超时,不可取。

仔细想想会发现,对于两个区间 [ l , r ] [l,r] [l,r] [ a , b ] [a,b] [a,b],若 a ≤ l ≤ r ≤ b a\le l\le r\le b alrb,且 [ l , r ] [l,r] [l,r]符合条件,则 [ a , b ] [a,b] [a,b]也肯定符合条件。

此时,可以考虑使用滑动窗口优化,则时间复杂度降至 O ( M N ) \mathcal O(MN) O(MN)

继续优化。在窗口滑动的过程中,每次移动左/右端点时考虑一次移动对当前符合条件的 i i i的数量的贡献,需要两个数组 c n t [ N ] \mathrm{cnt}[N] cnt[N](记录每个 A i A_i Ai B i B_i Bi符合条件的个数)和 i n v [ M + 1 ] [ …   ] \mathrm{inv}[M+1][\dots] inv[M+1][](预处理每个数值对应的所有元素下标)。

总时间复杂度为 O ( N + M ) \mathcal O(N+M) O(N+M),详见代码。

代码

#include 
#include 
#define maxn 200005
using namespace std;

vector<int> inv[maxn];
int cnt[maxn], ans[maxn];

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for(int i=0; i<n; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		inv[a].push_back(i);
		inv[b].push_back(i);
	}
	int left = n;
	for(int i=1, j=1; i<=m; i++)
	{
		for(; j <= m && left > 0; j++)
			for(int x: inv[j])
				if(++cnt[x] == 1)
					left --;
		if(left > 0) break;
		ans[j - i] ++, ans[m - i + 2] --;
		for(int x: inv[i])
			if(--cnt[x] == 0)
				left ++;
	}
	for(int i=1; i<=m; i++)
		printf("%d ", ans[i] += ans[i - 1]);
	return 0;
}

F - Find 4-cycle

题目大意

给定一个二分图 G G G,形如下:
AtCoder Beginner Contest 260 A~F 题解_第1张图片

图片来源: 二分图_百度百科

其中顶点集 U U U中的顶点数为 S S S V V V的顶点数为 T T T,总边数为 M M M(第 i i i条边连接 u i u_i ui v i v_i vi)。
请找出此图中任意长为 4 4 4的环。如果没有,输出-1

2 ≤ S ≤ 3 × 1 0 5 2\le S\le 3\times 10^5 2S3×105
2 ≤ T ≤ 3000 2\le T\le 3000 2T3000
4 ≤ M ≤ min ⁡ ( S × T , 3 × 1 0 5 ) 4\le M\le \min(S\times T,3\times 10^5) 4Mmin(S×T,3×105)
1 ≤ u i ≤ S < v i ≤ S + T 1\le u_i\le S1uiS<viS+T

输入格式

S   T   M S~T~M S T M
u 1   V 1 u_1~V_1 u1 V1
u 2   V 2 u_2~V_2 u2 V2
⋮ \vdots
u M   V M u_M~V_M uM VM

输出格式

如果有长为 4 4 4的环,输出其中四个顶点的编号(顺序随意,用空格分隔)。
如果没有,输出-1

样例

略,请自行前往AtCoder查看

分析

注意到样例中 T T T只有 3000 3000 3000 O ( T 2 ) = 9 × 1 0 6 \mathcal O(T^2)=9\times 10^6 O(T2)=9×106可以接受。
然后因为是二分图,所以长为 4 4 4的环肯定是在两个顶点集中各有两个点。

f ( x , y ) f(x,y) f(x,y)为目前发现的与点 x , y x,y x,y都相连的点,初始化为 − 1 -1 1(表示未发现)。
输入使用邻接表存储, G [ v ] G[v] G[v]存储连到 v v v的所有点,注意只需存顶点集 U U U G [ v ] G[v] G[v]即可。
再对于每个 v v v,依次枚举 G [ v ] G[v] G[v]中的两个点 ( x , y ) (x,y) (x,y),如果 f ( x , y ) = − 1 f(x,y)=-1 f(x,y)=1,则执行 f ( x , y ) : = v f(x,y):=v f(x,y):=v,如果不是 − 1 -1 1,则输出{x} {y} {v} {f(x,y)},结束程序。

时间复杂度约为 O ( T 2 ) \mathcal O(T^2) O(T2)

本题中的时间复杂度怎么算?
→ f ( x , y ) \to f(x,y) f(x,y)中不同 ( x , y ) (x,y) (x,y)的组合只有 T ( T − 1 ) = T 2 − T ≈ T 2 T(T-1)=T^2-T\approx T^2 T(T1)=T2TT2种。
→   \to~   根据鸽笼原理(又称抽屉原理),在最坏情况下, T 2 T^2 T2种组合都记录过 f f f之后,下一种组合无论是什么肯定都已经记录过 f f f,因此最坏时间复杂度为 O ( T 2 ) \mathcal O(T^2) O(T2),对于随机数据的平均时间复杂度远远小于这个值。

代码

#include 
#include 
#include 
#define maxs 300005
#define maxt 3005
using namespace std;

vector<int> G[maxs];
int f[maxt][maxt];

int main()
{
	int s, t, m;
	scanf("%d%d%d", &s, &t, &m);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		G[--u].push_back(--v - s);
	}
	memset(f, -1, sizeof(f));
	for(int i=0; i<s; i++)
		for(int j=0; j+1<G[i].size(); j++)
			for(int k=j+1; k<G[i].size(); k++)
			{
				int u = G[i][j], v = G[i][k];
				if(u > v) u ^= v ^= u ^= v;
				if(f[u][v] != -1)
				{
					printf("%d %d %d %d\n", f[u][v] + 1, i + 1, u + s + 1, v + s + 1);
					return 0;
				}
				f[u][v] = i;
			}
	puts("-1");
	return 0;
}

你可能感兴趣的:(算法竞赛,C++,#,AtCoder,算法,c++,AtCoder)