2022/4/30 五一集训#1
比赛链接:此处
A.石头剪刀布
- 虽然看出了是并查集,但由于太菜了没有看出来暴力成分……;
- 暴力枚举每一个孩子为法官时,如果与该孩子无关的条件逻辑自洽了,那么这个人就可能为法官;
- 如果合法的法官数为 \(0\),那么这一局不可能;如果合法的法官数大于 \(1\),那么说明有许多人作为法官都是可能的,所以法官无法确认;当且仅当合法法官数为 \(1\)时,才能确定法官;
- 法官确定的行数为所有情况下最后一个逻辑不自洽的地方;
AC code
#include<iostream>
#include<algorithm>
#include<exception>
#include<cstring>
#include<cmath>
#include<ios>
using namespace std;
const int N=505;
int n,m,t;
int ans[N<<2],last,f[N<<2];
struct memr{
int a,b,k;
}kid[N<<2];
int get(int x){
if(x==f[x]) return x;
return f[x]=get(f[x]);
}
void merge(int x,int y){
f[get(x)]=get(y);
return ;
}
void pre(){
for(int i=1;i<=3*n+5;++i)
f[i]=i;
return ;
}
void dfs(int i){
if(i>n) return ;
pre();
bool s=1;
for(int j=1;j<=m;++j){
if(kid[j].a==i || kid[j].b==i) continue;
int x=kid[j].a,y=kid[j].b;
int x_e=x+n,y_e=y+n,x_ey=x+2*n,y_ey=y+2*n;
if(kid[j].k==0){
if(get(x)==get(y_e) || get(x_e)==get(y)){
s=0,last=max(last,j);break;
}
merge(x,y);merge(x_e,y_e);merge(x_ey,y_ey);
}
else if(kid[j].k==1){
if(get(x)==get(y_e) || get(x)==get(y)){
s=0,last=max(last,j);break;
}
merge(x_e,y);merge(x,y_ey);merge(x_ey,y_e);
}
else{
if(get(x)==get(y_ey) || get(x)==get(y)){
s=0,last=max(last,j);break;
}
merge(x_ey,y);merge(x,y_e);merge(x_e,y_ey);
}
}
if(s) ans[++t]=i;
dfs(i+1);
return ;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
char opt;
for(int i=1;i<=m;++i){
cin>>kid[i].a>>opt>>kid[i].b;
++kid[i].a,++kid[i].b;
kid[i].k=(opt=='=')?0:((opt=='<')?-1:1);
}
t=last=0;
dfs(1);
if(t==0) puts("Impossible");
else if(t>1) puts("Can not determine");
else printf("Player %d can be determined to be the judge after %d lines\n",ans[t]-1,(n==1)?0:last);
}
return 0;
}
B.永无乡
- 虽然正解是线段树合并或者平衡树,但我是用分块 \(AC\) 的……;
纪念一下第一道在考场上 AC 的紫题;- 直接看这个也可、
Solution
-
我们看到 连通 两个字,考虑使用并查集来维护这一信息。
-
由于后面需要查询重要度排名 \(k\) 的岛屿,我们就需要考虑一下怎么较为快速的求出这一信息。
我们看到题目:“每座岛都有自己的独一无二的重要度”,再知道 \(1\le p_i\le n\),就想到这个重要度与岛的编号是一一对应且连续的。
这时我们就想到分块。将重要度分为 \(\sqrt n\times \sqrt n\) 个区间,每个区间记录当前连通块在这个区间里的岛屿个数。
此时我们需要一个 \(val[\ ]\) 数组来记录每个重要度对应的岛屿编号。
由于每次查询是在一个连通块内,所以我们需要一个 \(n\times \sqrt n\) 的数组,\(num_{i,j}\) 表示在并查集 \(get()\) 值为 \(i\) 的连通块里,第 \(j\) 个区间中有多少个岛屿。
这时分块的状态就设置完毕了。
-
由于分块的思想是大段维护,局部朴素,所以局部朴素部分先抛开(毕竟怎么写都是暴力),来看一下大段维护部分(查询第一个总岛屿数 \(< k\) 的区块)。
首先考虑到时间,由于 \(q\le 3\times 10^5\),我们需要以尽可能快的时间完成查询这一任务。
如果遍历来寻找,最坏复杂度是 \(O(\sqrt n)\),显然不太可行。这时想到二分( \(O(\log _ 2 n)\)的复杂度)。
如果二分,那么我们就需要将分块数组的定义改为 \(num_{i,j}\) 表示:在并查集 \(get()\) 值为 \(i\) 的连通块里,\(0\sim j\) 个区间中有多少个岛屿。也就是之前定义 的前缀和。
这样在查询时二分即可。
对于局部朴素,设求得区间及以前已经含有 \(x\) 个该连通块里的岛屿,在求得区间的下一个区间里遍历,找到的第 \(k-x\) 个 \(get()\) 值等于当前连通块编号的值即为答案。
-
由于在 \(q\) 个操作中还包含新修桥这一操作,所以我们考虑一下合并连通块时其他数组怎么处理。
可以想到,\(num\) 数组应该直接相加,这一操作不会破坏前缀和的性质。
AC code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
#define re register
const int N=1e5+10;
int n,m,q;
int rk[N],f[N],siz[N],val[N];
int dx,num[N][320];
inline int id(int x){
return (x-1)/dx+1;
}
int get(int x){
if(x==f[x]) return x;
return f[x]=get(f[x]);
}
void merge(int x,int y){
int a=get(x),b=get(y);
siz[b]+=siz[a];
for(re int i=1;i<=id(n);++i)
num[b][i]+=num[a][i];
f[a]=b;
return ;
}
int main(){
n=read(),m=read();
dx=sqrt(n);
for(re int i=1;i<=n;++i){
rk[i]=read(),f[i]=i,siz[i]=1,val[rk[i]]=i;
for(re int j=id(rk[i]);j<=id(n);++j)
num[i][j]++;
}
int u,v;
for(re int i=1;i<=m;++i){
u=read(),v=read();
merge(u,v);
}
q=read();
char opt[2];
while(q--){
scanf("%s",opt);
u=read(),v=read();
if(opt[0]=='B')
merge(u,v);
else{
u=get(u);
if(siz[u]<v){
puts("-1");
continue;
}
int l=1,r=id(n);
while(l<r){
int mid=(l+r)>>1;
if(num[u][mid]>=v)
r=mid;
else l=mid+1;
}
if(num[u][l]>=v)
--l;
v-=num[u][l];
for(re int i=l*dx+1;i<=(l+1)*dx;++i){
if(get(val[i])==u) v--;
if(!v){
printf("%d\n",val[i]);
break;
}
}
}
}
return 0;
}
C.[AGC004D] Teleporter
- 这是一道贪心
然而并没有看出来; - 首先我们猜测,当城市 \(1\) 为自环时最优,因此去掉从城市 \(1\) 出发的那条线,使整张图变成一棵以城市 \(1\) 为根节点的树,题目要求变为在 \(k\) 步以内到达城市 \(1\);
- 接下来使用贪心的思想,改动的传送阵数量最小,就意味着可以不用改动的城市尽量不改动;所以在深搜回溯时,每当一个节点距离其子树上最远的节点距离为 \(k-1\) 时,则意味着需要将这个节点连同它的子树一起直接接到城市 \(1\) 上;
AC code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
s=s*10+int(ch-'0');
ch=getchar();
}
return s*f;
}
const int N=1e5+10;
int n,k,ans=0;
int f[N];
int head[N],ver[N<<1],nxt[N<<1],tot=0;
inline void add(int x,int y){
ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
return ;
}
int dfs(int s,int dep){
int fd=dep;
for(int i=head[s];i;i=nxt[i])
fd=max(fd,dfs(ver[i],dep+1));
if(fd-dep==k-1){
if(f[s]!=1) f[s]=1,++ans;
return dep-1;
}
return fd;
}
int main(){
n=read(),k=read();
int v;
for(int i=1;i<=n;++i){
f[i]=read();
if(i!=1) add(f[i],i);
}
dfs(1,1);
if(f[1]!=1) ++ans;
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号