前言
又是一场压线不掉分的比赛。
Standings:2706
题目链接:Dashboard - EPIC Institute of Technology Round August 2024 (Div. 1 + Div. 2) - Codeforces
A. Distanced Coloring
题意:
给一个 n * m 的矩阵,涂色,要求任意两个相同颜色的块,他们横纵坐标差值的最大值不超过 k ,问涂色至少需要几种颜色。
思路:
很显然要让每种颜色的格子呈现一种最紧凑的排列方式,于是每隔 k 个位置放一样的颜色。
#include<cstdio>
#include<cstring>
using namespace std;
int T,n,m,k;
int min(int x,int y) { return x < y ? x : y ; }
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d%d",&n,&m,&k);
if(n < m)
{
int tmp = n;
n = m;
m = tmp;
}
int x = min(n,k);
printf("%d\n",x * min(k,m));
}
return 0;
}
B. Removals Game
题意:
给两个序列 a 和 b ,Alice 和 Bob 依次取走一个数,只能取两端的数,若两个人最后剩下的那个数相等则 Bob 获胜,反之 Alice 获胜。
思路:
容易发现 Bob 必须要 “跟着 Alice 取” 才能获胜,手模一下可以发现 a 和 b 必须是一致的或者互为逆序列才能让 Bob 获胜,否则 Alice 必胜。
(比赛时代码打得有点丑)
#include<cstdio>
#include<cstring>
using namespace std;
#define N 300005
int T,n,a[N],b[N];
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d",&n);
for (int i = 1;i <= n;++ i) scanf("%d",&a[i]);
for (int i = 1;i <= n;++ i) scanf("%d",&b[i]);
int la,ra,lb,rb,flag;
la = lb = 1,ra = rb = n,flag = 0;
while (la <= ra)
{
if((a[la] != b[lb] && a[la] != b[rb]) || (a[ra] != b[lb] && a[ra] != b[rb]))
{
flag = 1;
break;
}
if(a[la] == b[lb]) ++ la,++ lb;
else ++ la,-- rb;
}
if(flag) printf("Alice\n");
else printf("Bob\n");
}
return 0;
}
C. Black Circles
题意:
给你一些圆心,一个起点和一个终点,这些圆初始的半径是零,半径随时间均匀增加,你需要从起点走到终点,速度和半径增加的速率相同,判断走到终点的过程中会不会碰到圆。
思路:
显然要走直线,只需判断当人走到终点时有没有圆已经扩散到终点即可。
因为如果人和圆在终点前的某处会相交,那说明人到终点的时候圆一定也到了,因此我们只需判断终点处即可。
(比赛时看错了一个样例数据然后对题目各种猜想并百思不能其解浪费了很多时间。。。)
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define N 100005
int T,n;
long long x[N],y[N],xs,ys,xt,yt,dis;
long long count(long long x1,long long y1,long long x2,long long y2)
{
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d",&n);
for (int i = 1;i <= n;++ i) scanf("%lld%lld",&x[i],&y[i]);
scanf("%lld%lld%lld%lld",&xs,&ys,&xt,&yt);
dis = count(xs,ys,xt,yt);
int flag = 1;
for (int i = 1;i <= n;++ i)
{
long long now = count(x[i],y[i],xt,yt);
if(now <= dis)
{
flag = 0;
break;
}
}
if(flag) printf("YES\n");
else printf("NO\n");
}
return 0;
}
D1. DFS Checker (Easy Version)
题意:
给一棵从 1 ~ n 标号的满二叉树和一个长度为 n 的排列 p,每次询问交换 p 中的两个值,判断能否使得到的新序列成为这课满二叉树的一种 dfs 序。其中询问的交换操作是持久化的,
思路:
思考一下满二叉树的特性:所有非叶子节点 k 有且仅有 2 个儿子,且儿子的编号分别为 k + 1 和 k + son[k] + 1 (这里 son[k] 表示 k 的左边或右边子树的大小)。
想到这其实问题就迎刃而解了。我们暴力的想法就是扫一遍 p 数组,判断每一位 k 的儿子是否在合法的位置( k + 1 或 k + son[k] + 1 )上即可。这里询问只会改变两个数 x 和 y ,而对每个位置是否合法产生的影响只有 x ,y 及其前后的位置,因此我们可以做如下优化:统计当前 p 数组内合法的位置,每次询问更改对应位置上的 bool 值以及当前合法的位置总数,若某次操作后得到全部位置都合法,那就说明这是一个符合条件的 dfn 。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 500005
int T,n,q,p[N],f[N],id[N],son[N],now;
void change(int k,int w)
{
if(w)
{
if(!f[k]) f[k] = 1,++ now;
}
else
{
if(f[k]) f[k] = 0,-- now;
}
return;
}
void check(int k)
{
int x,y;
int bz = 0;
if(k >= (n + 1) / 2) bz = 1;
else
{
x = k * 2,y = k * 2 + 1;
if(id[k] + son[k] + 1 <= n)
{
if(p[id[k] + 1] == x && p[id[k] + son[k] + 1] == y)
{
if(f[x] && f[y]) bz = 1;
}
if(p[id[k] + 1] == y && p[id[k] + son[k] + 1] == x)
{
if(f[x] && f[y]) bz = 1;
}
}
}
change(k,bz);
return;
}
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&q);
for (int i = 1,x;i < n;++ i) scanf("%d",&x);
for (int i = 1;i <= n;++ i) scanf("%d",&p[i]),id[p[i]] = i,f[i] = 0;
for (int i = n; i ;-- i)
{
if(i >= (n + 1) / 2) son[i] = 0;
else son[i] = son[i * 2] * 2 + 1;
}
for (int i = n,x,y; i ;-- i)
{
if(i >= (n + 1) / 2) f[i] = 1;
else
{
x = i * 2,y = i * 2 + 1;
if(id[i] + son[i] + 1 > n) continue;
if(p[id[i] + 1] == x && p[id[i] + son[i] + 1] == y)
{
if(f[x] && f[y]) f[i] = 1;
}
if(p[id[i] + 1] == y && p[id[i] + son[i] + 1] == x)
{
if(f[x] && f[y]) f[i] = 1;
}
}
}
now = 0;
for (int i = 1;i <= n;++ i) now += (f[i]);
while (q --)
{
int x,y,tmp;
scanf("%d%d",&x,&y);
tmp = p[x],p[x] = p[y],p[y] = tmp;
x = p[x],y = p[y];
tmp = id[x],id[x] = id[y],id[y] = tmp;
while (x && y)
{
if(x > y) check(x),x >>= 1;
else if(x < y) check(y),y >>= 1;
else check(y),check(x),x >>= 1,y >>= 1;
}
if(now == n) printf("YES\n");
else printf("NO\n");
}
}
return 0;
}
D2. DFS Checker (Hard Version)
题意:
和 Easy Version 的区别:给的不是一棵满二叉树,而是更普遍的任意树。
思路:
这样一来每个节点的儿子数目就不确定了,若某个节点的孩子很多,询问又总是包含这个节点,那么势必会超时,我们需要考虑更普适的方法。
从 p 数组入手,思考一下合法的 dfn 数组每个位置上的点和前后位置上的点有什么关系:结论是 “要么前一个点是后一个点的父亲,要么前一个点是叶子节点并且这两个点的 lca 是后一个点的父亲” 。这个结论容易理解,是合法的 dfn 数组的必要条件,关键是这个结论是否是充分条件。
当然是。倘若数组 p 的每个位置都符合上述条件,那么我们从叶子节点往上思考,每一层都一定是合法的,这样整棵树也必然是可以构造出来的。
因此我们还是按照类似 Easy Version 的做法,统计当前 p 数组里有多少个合法位置,每次询问操作进行修改即可。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 300005
int T,n,q,f[N][22],st[N],dep[N],p[N],leaf[N],bz[N],lg[N],cnt,now;
struct Edge
{
int next,to;
}ed[N << 1];
void add(int u,int v)
{
ed[++ cnt].next = st[u];
ed[cnt].to = v;
st[u] = cnt;
return;
}
void dfs(int x,int fa)
{
dep[x] = dep[fa] + 1;
for (int i = 1;(1 << i) <= dep[x];++ i)
f[x][i] = f[f[x][i - 1]][i - 1];
int flag = 1;
for (int i = st[x]; ~i ;i = ed[i].next)
if(ed[i].to != fa)
{
dfs(ed[i].to,x);
flag = 0;
}
leaf[x] = flag;
return;
}
int lca(int x,int y)
{
if(dep[x] < dep[y])
{
int tmp = x;
x = y,y = tmp;
}
while (dep[x] > dep[y]) x = f[x][lg[dep[x] - dep[y]]];
if(x == y) return x;
for (int i = lg[dep[x]];i >= 0;-- i)
if(f[x][i] != f[y][i])
x = f[x][i],y = f[y][i];
return f[x][0];
}
int check(int x)
{
int f1,f2;
f1 = f2 = 0;
if(x > 1)
{
if(p[x - 1] == f[p[x]][0]) f1 = 1;
else if(lca(p[x - 1],p[x]) == f[p[x]][0] && leaf[p[x - 1]]) f1 = 1;
}
else f1 = 1;
if(x < n)
{
if(p[x] == f[p[x + 1]][0]) f2 = 1;
else if(lca(p[x],p[x + 1]) == f[p[x + 1]][0] && leaf[p[x]]) f2 = 1;
}
else f2 = 1;
return (f1 & f2) ;
}
void change(int k,int w)
{
if(k < 1 || k > n) return;
if(!bz[k] && w) bz[k] = 1,++ now;
else if(bz[k] && !w) bz[k] = 0,-- now;
return;
}
int main()
{
lg[1] = 0;
for (int i = 2;i <= 300000;++ i) lg[i] = lg[i >> 1] + 1;
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&q),st[1] = -1,dep[0] = f[1][0] = cnt = now = 0;
for (int i = 2;i <= n;++ i) scanf("%d",&f[i][0]),add(i,f[i][0]),add(f[i][0],i),st[i] = -1;
dfs(1,0);
for (int i = 1;i <= n;++ i) scanf("%d",&p[i]);
for (int i = 1;i <= n;++ i) bz[i] = check(i),now += bz[i];
while (q --)
{
int x,y,tmp;
scanf("%d%d",&x,&y);
tmp = p[x],p[x] = p[y],p[y] = tmp;
change(x,0),change(x - 1,0),change(x + 1,0);
change(y,0),change(y - 1,0),change(y + 1,0);
change(x,check(x)),change(y,check(y));
if(x > 1) change(x - 1,check(x - 1));
if(x < n) change(x + 1,check(x + 1));
if(y > 1) change(y - 1,check(y - 1));
if(y < n) change(y + 1,check(y + 1));
if(now == n) printf("YES\n");
else printf("NO\n");
}
}
return 0;
}
E. Cosmic Rays
题意:
给一个序列 s ,每秒钟宇宙射线都会辐射这个序列,使得所有 的 消失。序列 s 以 (a,b)对的形式给出,表示每个 b 有 a 个,对于所有的 i = 1,2,...,n ,计算前 i 组数形成的序列 s 变成空序列花费的时间。
思路:
一眼单调栈,维护每组数前面的 “佼佼者” ,即到这组数之前能最后剩下的那组数。
若当前这组数可以直接或者通过跨越与前面的数合并,那么更新当前的佼佼者。
若当前这组数的个数没有前面的多,那意味着无法跨越到前面去合并。
单调栈维护即可。
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;
#define N 300005
int T,n;
long long a[N],b[N],ans;
stack<long long> st;
long long max(long long x,long long y) { return x > y ? x : y ; }
int main()
{
int num = 0;
scanf("%d",&T);
while (T --)
{
scanf("%d",&n),ans = 0ll;
for (int i = 1;i <= n;++ i) scanf("%lld%lld",&a[i],&b[i]);
while (!st.empty()) st.pop();
for (int i = 1;i <= n;++ i)
{
long long now = 0ll;
while (!st.empty() && ((b[st.top()] != b[i] && a[i] >= a[st.top()]) || (b[st.top()] == b[i])))
{
if(b[st.top()] != b[i]) now = max(now,a[st.top()]);
else
{
int las = st.top();
a[i] = a[i] + a[las] - now;
}
st.pop();
}
st.push(i);
ans = max(ans,a[i]);
++ num;
printf("%lld ",ans);
}
printf("\n");
}
return 0;
}
总结
貌似还是前面的题目太容易卡了,div 2 后面的 EF 题很多时候都是可做的但是没有时间,甚至有时候 D 都没开出来,可能需要多练练 B 这种小巧玲珑的思维题。