比赛的时候只做出了A-D(太菜 ),然后到现在才补掉了这套题(太懒 ),以下是各题简单叙述,前四题代码就不贴了。
A
Diverse Strings
题目大意
对于给定的字符串,如果是由一段连续的不重复的字母组成,则输出“yes”。
解题思路
直接排序后,遍历检查即可。
B
Parity Alternated Deletions
题目大意
给出n个数,每次可以任意删除一个与上次不同奇偶性的数字(第一次可以随便选),问最后剩余的数字的和最小是多少。
解题思路
统计出序列中奇数和偶数的个数a,b,然后分情况:
- abs(a-b)<=1 输出0即可。
- a-b>1,说明会剩下a-b-1个奇数,找出最小的那a-b-1个奇数即可。
- b-a>1,说明会剩下b-a-1个偶数,找出最小的那b-a-1个偶数即可 。
注意开longlong
C
Two Shuffled Sequences
题目大意
给出n个数,问能否拆成两个序列,一个严格递增,一个严格递增(可以任意重排)。任意排列这点就大大简化问题了。
解题思路
考虑到可以任意排序,以及严格这两个关键点,读入的时候用一个桶记录所有数字出现的次数。
- 某个数字出现次数大于3说明肯定无解(必然有一个序列会出现两个数字相等的情况)。
- 某个数字出现次数等于2,分别放在两个序列即可。
- 出现一次的数,随便放即可。(长度为0或1的序列可被任意认为递增或者递减)。
D
Equalize Them All
题目大意
给出n个数字的序列,每次可以选择相邻的两个元素,使其中一个元素加上或者减去这两个元素差的绝对值,询问最少需要几次操作,使得所有数字相等,并输出每次操作。ai=ai±|ai−aj|。
解题思路
显然的 贪心题,先找出次数出现最多的那个元素的位置(用桶数组来维护,同C题),然后从那个位置分别向两端扩展,每次让边上的一个元素与它相等即可。
E
Median String
看F题过的人比较多,就没看E这道字符串题,不过感觉挺简单的 。。。
题目大意
给出两个字符串,让你给出排在两个字符串字典序中间的字符串。
解题思路
把字符串当作一个26进制数,由于长度达到2e5,所以需要用大数模拟。从后往前遍历,对于第i位,如果ai+bi是偶数,直接除2即可,如果是奇数,就要向后一位进13(26/2),然后处理一下后一位的进位(/26,%26)。(想想十进制下的奇数除以2)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 2e5 + 5;
char s[N], t[N];
int ans[N];
int main()
{
int k;
scanf("%d%s%s",&k,s,t);
for(int i=k-1;i>=0;i--)
{
int a = s[i] - 97, b = t[i] - 97;
ans[i] = a + b;
if(ans[i]&1)
{
ans[i] = (ans[i]-1) >> 1;
ans[i+1] += 13;
ans[i] += ans[i+1]/26;
ans[i+1] %= 26;
}
else ans[i] >>= 1;
}
for(int i=0;i<k;i++) printf("%c",ans[i]+97);
puts("");
return 0;
}
F
Graph Without Long Directed Paths
题目大意
给出n个点m条边的无环图,问你能否构造出一个有向图(将每条给出的双向边改成有向边),使得任意两点间距离不能大于等于2。
解题思路
二分图判定的染色法,对于一个顶点,不可能同时有入度和出度,要么只有入边,要么只有出边,所以只需要dfs对每一个顶点染色(1代表只有入度,-1代表只有出度)。
应该用vector存图的,然而我强行用链式前向星,没法记录编号,所以代码很丑陋。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 2e5 + 5;
int n, m, cnt, head[N], color[N], ans[N];
bool flag = true;
struct node
{
int to, next, dir, id;
}e[N<<1];
void add(int u,int v,int w,int id)
{
e[++cnt].next = head[u];
e[cnt].to = v;
e[cnt].dir = w;
e[cnt].id = id;
head[u] = cnt;
}
void dfs(int u,int col)
{
if(!flag) return;
color[u] = col;
for(int i=head[u];i;i=e[i].next)
{
int v = e[i].to;
if(color[v]==col)
{
flag = false; //全局变量判断无解的情况
return;
}
int d; //奇怪的分类讨论
if(!e[i].dir&&col==1) d = 1;
else if(!e[i].dir&&col==-1) d = 0;
else if(e[i].dir&&col==1) d = 0;
else d = 1;
ans[e[i].id] = d; //这句话不能放在if(!color[v])里面,因为这个一直wa
if(!color[v]) dfs(v,-col);
if(!flag) return;
}
}
int main()
{
scanf("%d%d",&n,&m);
int u, v;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
add(u,v,0,i);
add(v,u,1,i);
}
dfs(1,1);
if(!flag) puts("NO");
else
{
puts("YES");
for(int i=1;i<=m;i++) printf("%d",ans[i]);
puts("");
}
return 0;
}
G
Two Merged Sequences
C的强化版,不能重排了。
题目大意
对于给定的n个数组成的序列,问能否按照原序拆成一个严格递增,一个严格递减的序列。cf给的标签是贪心和dp。
解题思路
如果是dp的话,用dp[i][0]记录第i个数字放在升序序列中时,此时降序序列的最小元素的大小,dp[i][1]记录第i个数字放在降序序列中时,此时升序序列的最大元素的大小,然后转移即可。(有点绕)当i放在升序序列时,a[i]就是升序序列的最大元素,所以只需要记录降序序列即可,do[i][1]同理。
考虑贪心,每次也是维护升序序列的最大值up和降序序列的最小值down,然后讨论。
- a[i]>up&&a[i]>down 只能放在升序序列,那就放呗,然后更新up=a[i]
- a[i]<down&&a[i]<up 只能放在降序序列,那也放呗,然后down=a[i]
- a[i]>up&&a[i]<down 两个地方都能放,那考虑后一个元素(给后面留条路),如果a[i+1]大于a[i],就放在升序,否则放在降序。(核心)
- 哪都放不了,那就输出NO。
下面是贪心代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 2e5 + 5;
int n, a[N];
bool ans[N], flag = true;
int main()
{
int pos = 0, minn = 0x3f3f3f3f;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int up = -1, down = N;
for(int i=1;i<=n;i++)
{
if(a[i]<down&&a[i]>up)
{
if(a[i+1]>a[i]||i==n)
{
up = a[i];
ans[i] = 0;
}
else
{
down = a[i];
ans[i] = 1;
}
}
else if(a[i]<down)
{
down = a[i];
ans[i] = 1;
}
else if(a[i]>up)
{
up = a[i];
ans[i] = 0;
}
else
{
flag = false;
break;
}
}
if(!flag) puts("NO");
else
{
puts("YES");
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
puts("");
}
return 0;
}