【题解】Codeforces Round #798 (Div. 2)
本篇为 Codeforces Round #798 (Div. 2) 也就是 CF1689 的题解,因本人水平比较菜,所以只有前四题
A.Lex String
题目描述
原题面
给定两个字符串 \(a,b\),要求通过以下的两种操作构造一个新的字符串 \(c\),使得 \(c\) 的字典序最小,并且要求不能连续使用某一种操作超过 \(k\) 次
1.从 \(a\) 中任选一个字符插入到 \(c\) 的末尾
2.从 \(b\) 中任选一个字符插入到 \(c\) 的末尾
若某一个字符串为空则认为构造结束。
若字符串 \(x\) 比字符串 \(y\) 字典序小则必定满足以下两种情况之一:
1.\(x\) 为 \(y\) 的前缀,且 \(x \not= y\)
2.从左到右的顺序,\(x\) 与 \(y\) 第一个不同的位置上 \(x\) 的字符的字典序比 \(y\) 的字符的字典序小
题目分析
我们首先可以发现一点:所谓的插入末尾与插入开头并没有任何的区别
所以为了字典序小我们肯定每一次选择 \(a,b\) 中字典序最小的字符,如果超过了 \(k\) 次就选另一个字符串的,下一次再选它就好了
需要注意这里的字典序小不包含:仅仅是字符串长度更小,所以不用考虑这样的构造出的字符串长度上的问题
代码详解
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
char a[MAXN],b[MAXN];
int main(){
int t;
cin>>t;
while(t--){
int n,m,k;
cin>>n>>m>>k;
for(int i=1; i<=n; i++){
cin>>a[i];
}
for(int i=1; i<=m; i++){
cin>>b[i];
}
sort(a+1,a+n+1);
sort(b+1,b+m+1);
int l = 1,r = 1,tmp = 0;
bool flag = false;
while(l <= n && r <= m){
if(a[l] < b[r]){
if(flag){
cout<<a[l++];
flag = false;
tmp = 1;
}
else{
if(tmp < k){
cout<<a[l++];
tmp++;
}
else{
cout<<b[r++];
tmp = 1;
flag = true;
}
}
}
else if(a[l] >= b[r]){
if(!flag){
cout<<b[r++];
flag = true;
tmp = 1;
}
else{
if(tmp < k){
cout<<b[r++];
tmp++;
}
else{
cout<<a[l++];
tmp=1;
flag = false;
}
}
}
}
cout<<endl;
}
return 0;
}
其实代码有一点复杂,但是其实思路很简单
B. Mystic Permutation
题目描述
原题面
给定一个 \([1,n]\) 的排列,要求你构造一个 \([1,n]\) 的排列,使得这两个排列之间的任意一个相同位置的元素都不相同,且满足这个排列的字典序最小。
如果无法构造出这样的序列则输出 -1
题目分析
一个非常正常的想法:把元素从小到大插入,如果遇到了相同的或者用过的就跳过
但是这样会出现一种情况那就是序列的最后一个元素是 \(n\),那么放到最后一个元素的时候就只剩下了 \(n\),那么就无论如何也无法放置了
这个问题也非常好解决:最后的这个元素 \(n\) 我们肯定不能放在很靠前的位置,因为这样很明显字典序不会最小了,那么为了使得这个元素放得下我们就放在 \(n-1\) 的位置上,让 \(n-1\) 的位置上原本应该放的元素放到 \(n\) 上,这样能保证前 \(n-2\) 个一定是最小的,而不存在一种别的方法使得可以使得最后的两个元素比我们现在放置的更小而且前 \(n-2\) 个依旧保持最小。
有一说一样例是真强,不然的话我应该发现不了这一点。
代码详解
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e3+5;
bool flag[MAXN];
int a[MAXN];
int main(){
int t;
cin>>t;
while(t--){
memset(flag,false,sizeof(flag));
int n;
cin>>n;
if(n == 1){
cin>>a[1];
printf("-1\n");
continue;
}
for(int i=1; i<=n; i++){
cin>>a[i];
}
for(int i=1; i<=n; i++){
if(i == n - 1 && !flag[a[n]]){
printf("%d ",a[n]);
flag[a[n]] = true;
}
for(int j=1; j<=n; j++){
if(!flag[j] && j != a[i]){
printf("%d ",j);
flag[j] = true;
break;
}
}
}
cout<<endl;
}
return 0;
}
因为数据范围比较小,所以为了写的来更加舒服就写了这样非常暴力的做法,但是我们应该理解起来更容易一些。
C. Infected Tree
题目描述
原题面
给定一棵以 \(1\) 号节点为根的二叉树,现在 \(1\) 号节点感染了病毒,病毒每一回合都会去感染与该节点直接相连的节点,而你在这一回合里可以选择删除任意一个没有被病毒感染的点,这样就断开了它与其直接相连的点得关系,询问最多可以有多少不被病毒感染的点,被删除的点不算做不被病毒感染的点
题目分析
我们考虑为了尽可能多的保留节点,我们每一次肯定是会删除被病毒感染的节点的某个子节点,而删除之后我们的问题就可以转化为它的另一个儿子(注意为二叉树)根被感染病毒能保留多少节点的子问题,那么这很明显就可以考虑一下 \(DP\)。\(DP\) 状态也很简单:\(dp[i]\) 表示以 \(i\) 为根的子树若 \(i\) 被感染病毒,最多能保留多少节点
那么下面就是考虑转移了,转移很明显就是我们考虑删除哪一个 \(i\) 的子节点就好了,因为是二叉树所以不用考虑更多的情况。那么我们就分别考虑究竟删除哪一个子节点,假设删除 \(son_1\),那么相当于在 \(son_1\) 的子树里我们可以保留下 \(size[son_1] - 1\) 个节点,\(size[i]\) 代表以 \(i\) 为根的子树的大小(包含节点 \(i\)),而在 \(son_2\) 的子树里,我们依旧可以保留下 \(dp[son_2]\) 个节点,所以最终的状态就是:
需要注意可能该子树为空,那么 \(size[son] - 1\) 就是一个负数,所以需要对 \(0\) 取 \(\max\),避免这种情况
代码详解
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5+5;
struct edge{
int nxt,to;
edge(){}
edge(int _nxt,int _to){
nxt = _nxt,to = _to;
}
}e[2 * MAXN];
int ans = 0,cnt,head[MAXN],dp[MAXN],sz[MAXN];
//dp[now] 表示以 now 为根的子树,假设 now 被感染,最多保留多少个
void add_edge(int from,int to){
e[++cnt] = edge(head[from],to);
head[from] = cnt;
}
void dfs(int now,int fa){
sz[now] = 1;
int son[3] = {0,0,0};
int tot = 0;
for(int i=head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(to == fa) continue;
son[++tot] = to;
dfs(to,now);
sz[now] += sz[to];
}
//去 son[1] / 去 son[2]
dp[now] = max(dp[son[1]] + max(sz[son[2]] - 1,0),dp[son[2]] + max(sz[son[1]] - 1,0));
}
int main(){
int t;
cin>>t;
while(t--){
memset(head,0,sizeof(head));
memset(dp,0,sizeof(dp));
memset(sz,0,sizeof(sz));
ans = 0;
int n;
cin>>n;
for(int i=1; i<n; i++){
int from,to;
cin>>from>>to;
add_edge(from,to);
add_edge(to,from);
}
dfs(1,0);
printf("%d\n",dp[1]);
}
return 0;
}
先进行 \(dfs\) 再更新 \(sz\) 和 \(dp\),以及多组数据所以每次需要将各种数组清零,清零 \(head\) 数组也相当于清零边的数组了
D. Lena and Matrix
题目描述
原题面
给定一个 \(n\times m\) 的矩阵,每个位置都有一个颜色,分别为黑色或白色,要求你选择一个位置(不计这个位置上是什么颜色),使得整个位置到所有黑色格子的曼哈顿距离的最大值最小。
曼哈顿距离即:\(|x_1 - x_2| + |y_1 - y_2|\)
题目分析
要求的是最大值最小,其实发现这个最大值不是和任意一个黑色格子的距离都有可能成为最大值的,能成为最大距离的黑色格子一定是最左上、右下、左下、右上的黑格子,可以发现如果不是这四个格子,那么把距离转化到这四个黑色格子上都一定可以增加
下面就是如何寻找着四个黑色格子的问题了,我感觉这个思路还是很神奇的。
考虑如果一个格子离左上角越近,\(x\) 越小,\(y\) 越小,而且 \(x\) 与 \(y\) 的减小,对其离左上角的距离产生的贡献是一样的,那么就直接令这个贡献为 \(x+y\),那么最左上角的点也就一定是贡献最小的点,最右下角的点也一定是贡献最大的点,这就解决了两个。
考虑如果一个格子里右上角越近,\(x\) 越大,\(y\) 越小,而且它们产生的贡献也都是一样的,所以就考虑直接令贡献为 \(x-y\),这样最右上的点也就是 \(x-y\) 最大点,最左下的点也就是 \(x-y\) 最小的点,至此四个点全部解决了。
那么就是要找一个位置使得这个位置到这四个位置的距离最大值最小,那么就直接枚举每一个位置寻找一下就好了
代码详解
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9+5;
struct node{
int x,y;
node(){}
node(int _x,int _y){
x = _x,y = _y;
}
};
int main(){
int t;
cin>>t;
while(t--){
node a[6];
bool flag[6];
memset(flag,0,sizeof(flag));
int n,m;
cin>>n>>m;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
char h;
cin>>h;
if(h == 'B'){
if(i + j > a[1].x + a[1].y || !flag[1]) a[1] = node(i,j),flag[1] = true;
//越在右下角 i + j 越大
if(i + j < a[2].x + a[2].y || !flag[2]) a[2] = node(i,j),flag[2] = true;
//越在左上角 i + j 越小
if(i - j > a[3].x - a[3].y || !flag[3]) a[3] = node(i,j),flag[3] = true;
//越在右上角 i - j 越大
if(i - j < a[4].x - a[4].y || !flag[4]) a[4] = node(i,j),flag[4] = true;
//越在左下角 i - j 越小
}
}
}
int res = INF;
node ans;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
int h = 0;
for(int k=1; k<=4; k++){
h = max(h,abs(i - a[k].x) + abs(j - a[k].y));
}
if(h < res){
ans = node(i,j);
res = h;
}
}
}
printf("%d %d\n",ans.x,ans.y);
}
return 0;
}
先去找四个点找到了就每个位置枚举一下,最后输出就好了