Codeforces Global Round 13
Codeforces Global Round 13
前言
吐了,本来是上分局的。。
这么简单一套题我都能打成这个傻逼样子。。。
A 略
B Minimal Cost
题意
给一个n行(1e6+2)列的图,用(i,j)表示i行j列上的点,\(1\leq i \leq n,0\leq j\leq 1e6+1\),然后第i行的\(a_i\)是障碍,不能通过。现在可以移动障碍,水平移动一格花费v,上下移动一格花费u,要使(1,0)能到达(n,1e6+1),求最小花费。\(1\leq n \leq 100,1\leq a_i \leq {10}^{6}\) 。
题解
做法很简单,但我当时太蠢了没想到。这题过了我应该就不至于掉分了。
首先因为\(1\leq a_i\leq 1e6\),所以j=0和j=1e6+1两列都是无障碍的。
我们要做的就是能从第0列到第1e6+1列。
这样就少了很多特殊情况了,显然只需要让某相邻两行的a的差的绝对值大于1或者让某一行空出来。
于是分类讨论一下就行了。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
int n;
int a[110];
void MAIN(){
int u,v;
n=read();u=read();v=read();
for(int i=1;i<=n;++i){
a[i]=read();
}
int ans=min(u,v)+v;
for(int i=2;i<=n;++i){
if(abs(a[i]-a[i-1])>1) ans=min(ans,0);
}
for(int i=2;i<=n;++i){
if(abs(a[i]-a[i-1])==1) ans=min(ans,min(u,v));
}
printf("%d\n",ans);
return;
}
int main(){
int ttt=read();
while(ttt--) MAIN();
return 0;
}
C 略
D 略
E Fib-tree
题意
如果一棵树点数是斐波那契数列的某一项,并且满足点数为1或者能分成两个斐波那契树,则称这棵树是斐波那契树。现在给定一棵树,判断是否是斐波那契树。\(1\leq n \leq 200000\) 。
题解
首先如果树点数是\(Fib_{k+2}\),那么被分出的两颗树的点数一定分别是\(Fib_{k+1},Fib_k\)。
然后会发现一棵\(Fib_{k+2}\)树,可能有0-2条能把树分成\(Fib_{k+1},Fib_{k}\)的边。
如果有0条,那肯定是不行了。
如果有1条,那只能把这条边拆掉了。
如果有两条,那么不管先拆哪条,最终结果都是一样的(这个看起来挺明显的,不清楚怎么证)。
然后一直暴力拆就可以了。因为Fib数列长度是log级别的,暴力拆分效率也是\(O(nlogn)\)的。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
int n,cnt;
int fib[N],mp[N];
int hed[N];
int vis[N],used[N];
struct edge{
int r,nxt,id;
}e[N<<1];
void insert(int u,int v,int w){
e[++cnt].r=v;e[cnt].nxt=hed[u];hed[u]=cnt;e[cnt].id=w;
}
int fa[N],sz[N];
void dfs1(int x,int t){
vis[x]=t;sz[x]=1;
for(int i=hed[x];i;i=e[i].nxt){
if(used[e[i].id]) continue;
if(e[i].r==fa[x]) continue;
fa[e[i].r]=x;
dfs1(e[i].r,t);
sz[x]+=sz[e[i].r];
}
}
int X;
int tp;
int q[N];
void dfs2(int x){
for(int i=hed[x];i;i=e[i].nxt){
if(used[e[i].id]) continue;
if(e[i].r==fa[x]) continue;
dfs2(e[i].r);
if(mp[sz[e[i].r]]==X-1||mp[sz[e[i].r]]==X-2){
q[++tp]=e[i].id;
}
}
}
void MAIN(){
n=read();
fib[1]=1;fib[2]=2;
mp[1]=1;mp[2]=2;
for(int i=3;;++i){
fib[i]=fib[i-1]+fib[i-2];
if(fib[i]>n) break;
mp[fib[i]]=i;
}
int u,v;
for(int i=1;i<n;++i){
u=read();v=read();
insert(u,v,i);
insert(v,u,i);
}
int mx=n,t=0;
int flag=0;
while(mx>3){
++t;mx=0;
for(int i=1;i<=n;++i){
if(vis[i]!=t){
fa[i]=0;
dfs1(i,t);
mx=max(mx,sz[i]);
if(mp[sz[i]]==0){
flag=1;break;
}
if(sz[i]<=3) continue;
tp=0;X=mp[sz[i]];
dfs2(i);
if(!tp){
flag=1;break;
}
while(tp>0){
used[q[tp]]=1;
--tp;
}
}
}
if(flag==1) break;
}
if(flag) puts("NO");
else puts("YES");
return;
}
int main(){
int ttt=1;
while(ttt--) MAIN();
return 0;
}
F Magnets
题意
n个磁铁,分为3类,S,N,-(没有磁性),现在要从中找到所有没有磁性的。可以进行\(n+\lfloor log_2n \rfloor\)次询问,有一个仪器,每次询问可以在仪器右边放若干个磁铁,左边放若干个磁铁,仪器可以测出两边磁铁产生的磁力(\(S_lS_r+N_lN_r-S_lN_r-N_lS_r\)),数据保证至少有1个没有磁性和2个有磁性的。仪器的测量上限是n,也就是说测出的值不能超过n,否则仪器会崩溃。\(1\leq n \leq 2000\) 。
题解
这题思路不难,但出题人是真的抠门。询问次数刚好卡满,一次都没多。
我当时已经想到了,但是比要求的多询问了两次。。本来能上大分的(委屈屈QAQ)
首先肯定要找到一个有磁性的,这样其他的磁铁可以直接通过询问这个磁铁来判断。
要找磁铁,肯定要使里能测出来。
从左到右枚举,询问第i个磁铁跟后面所有的磁铁,这样测出的值显然不会超过量程,出现测量值不为0就找到了。
这样我们就在不到n次内找到了一个磁铁,设为K。
此时我们已知K和(K+1,K+2,...,n)的答案,然后依次询问K和(K+2,K+3,..,n)....(n)。
这样加上之前找磁铁的次数,一共n-1次。并且我们已经能判断K到n所有磁铁是否有磁性。
然后考虑之前的怎么弄,前面的询问答案肯定是0,这些询问没有有用信息。
但是有一个性质:前面最多有一个有磁性的(在询问那个磁铁的时候,右边SN一样多)。
这样就可以二分了,二分的次数是\(\lceil log_2n \rceil\)。加上n-1次次数刚好够。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
int n,res,K,T,tp;
int q[N];
int b[N];
void MAIN(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
printf("? %d %d\n",1,n-i);
fflush(stdout);
printf("%d\n",i);
fflush(stdout);
for(int j=i+1;j<=n;++j){
printf("%d ",j);
}
printf("\n");
fflush(stdout);
scanf("%d",&res);
if(res!=0){
K=i;
b[K+1]=res;
break;
}
}
for(int i=K+2;i<=n;++i){
printf("? %d %d\n",1,n-i+1);
fflush(stdout);
printf("%d\n",K);
fflush(stdout);
for(int j=i;j<=n;++j){
printf("%d ",j);
}
printf("\n");
fflush(stdout);
scanf("%d",&res);
b[i]=res;
}
tp=0;b[n+1]=0;
for(int i=n;i>K;--i){
if(b[i]==b[i+1]) q[++tp]=i;
}
T=0;
if(K>1){
n=K-1;
int l=1,r=n,mid,fff=0;
while(l<r){
mid=(l+r)>>1;
printf("? %d %d\n",1,mid-l+1);
fflush(stdout);
printf("%d\n",K);
fflush(stdout);
for(int j=l;j<=mid;++j){
printf("%d ",j);
}
printf("\n");
fflush(stdout);
scanf("%d",&res);
if(res==0) l=mid+1;
else {
r=mid;
fff=1;
}
}
T=l;
if(fff==0){
printf("? %d %d\n",1,1);
fflush(stdout);
printf("%d\n",K);
fflush(stdout);
printf("%d\n",T);
fflush(stdout);
scanf("%d",&res);
fff=res;
}
if(fff==0) {
for(int j=1;j<=n;++j) q[++tp]=j;
}
else {
for(int j=1;j<=n;++j) if(j!=T) q[++tp]=j;
}
}
printf("! %d",tp);
for(int i=1;i<=tp;++i) printf(" %d",q[i]);
printf("\n");
fflush(stdout);
return;
}
int main(){
int ttt;scanf("%d",&ttt);
while(ttt--) MAIN();
return 0;
}
G Switch and Flip
题意
有一个长度为n排列\(c_i\),最多可以进行n+1次操作,使得\(c_i=i\)。每次操作选择两个不同的数i,j(\(1\leq i,j \leq n\)),交换\(c_i\)和\(c_j\)的值,并且\(c_i\)和\(c_j\)都乘上-1。\(1\leq n \leq 200000\) 。
题解
比赛的时候没做到这题,现在看来这比F还水啊QAQ。
一看题目要求O(n)级别的次数来换完,容易想到之歌跟置换有关。
于是先把所有循环给预处理出来。
然后分两种情况讨论。
第1种情况,只有1个循环长度为n的循环。我们设其为(2,3,4,...,n,1)。
然后有一种比较显然的换法。
(2,3,4,5,...,n,1)->(2,-1,4,5,...,n,-3)->(2,-1,3,5,...,n,-4)->...->(2,-1,3,4,...,n-1,-n)->(n,-1,3,4,...,n-1,-2)->(1,-n,3,4,...,n-1,-2)->(1,2,3,.....,n)
第2种情况,有多个循环。这种情况我们只需考虑如何合并两个循环。设为(a2,a3,a4,..,an,a1)(b2,b3,b4,..,bn,b1)
合并方法。
(a2,a3,a4,..,an,a1)(b2,b3,b4,..,bn,b1)->(-b2,a3,a4,..,an,a1)(-a2,b3,b4,..,bn,b1)->...->(-b1,a3,a4,..,an,a1)(-a2,b2,b3,..,bn)->..->(-b1,a2,a3,..,an)(-a1,b2,b3,..,bn)->(a1,a2,a3,..,an)(b1,b2,b3,..,bn)
如果循环个数是偶数个,两两合并即可。如果是奇数个,那么最后多出一个,最后随便取一个循环长度为1的循环来做即可。
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
int n,cnt;
int a[N];
int len[N];
bool vis[N];
vector<int> p[N];
int t;
int l[N],r[N];
void MAIN(){
n=read();cnt=0;
for(int i=1;i<=n;++i) {
a[i]=read();
vis[i]=0;
}
//for(int i=1;i<=n;++i) cout<<a[i]<<" ";puts("");
for(int i=1;i<=n;++i){
if(!vis[i]){
++cnt;
p[cnt].clear();
len[cnt]=0;
for(int j=i;;j=a[j]){
if(!vis[j]){
vis[j]=1;
++len[cnt];
p[cnt].push_back(j);
}
else break;
}
}
}
t=0;
if(cnt==1){
for(int i=1;i<len[cnt]-1;++i){
++t;
l[t]=p[cnt][i];r[t]=p[cnt][len[cnt]-1];
}
++t;
l[t]=p[cnt][0];r[t]=p[cnt][len[cnt]-1];
++t;
l[t]=p[cnt][0];r[t]=p[cnt][1];
++t;
l[t]=p[cnt][1];r[t]=p[cnt][len[cnt]-1];
}
else{
while(cnt>=2){
++t;
l[t]=p[cnt][0];r[t]=p[cnt-1][0];
for(int i=1;i<len[cnt];++i){
++t;
l[t]=p[cnt][i];r[t]=p[cnt-1][0];
}
for(int i=1;i<len[cnt-1];++i){
++t;
l[t]=p[cnt-1][i];r[t]=p[cnt][0];
}
++t;
l[t]=p[cnt][0];r[t]=p[cnt-1][0];
cnt-=2;
}
if(cnt==1){
++cnt;
len[cnt]=1;
++t;
l[t]=p[cnt][0];r[t]=p[cnt-1][0];
for(int i=1;i<len[cnt];++i){
++t;
l[t]=p[cnt][i];r[t]=p[cnt-1][0];
}
for(int i=1;i<len[cnt-1];++i){
++t;
l[t]=p[cnt-1][i];r[t]=p[cnt][0];
}
++t;
l[t]=p[cnt][0];r[t]=p[cnt-1][0];
cnt-=2;
}
}
printf("%d\n",t);
for(int i=1;i<=t;++i){
printf("%d %d\n",l[i],r[i]);
}
return;
}
int main(){
int ttt=1;
while(ttt--) MAIN();
return 0;
}
H Yuezheng Ling and Dynamic Tree
题意
给定一个数组\(a_i(2\leq i \leq n)\),表示树中节点的父亲。现在要维护两种操作,一共Q次。1.给定\(l,r,x\),令\([l,r]\)区间内的\(a_i=max(1,a_i-x)\)。2.给定\(u,v\),求两点在树中的最近公共祖先。\(1\leq n,Q \leq 100000\) 。
题解
这东西一看就不能建树。。。
再仔细一想就很容易发现这跟弹飞绵羊那道题几乎一样。。。
先分块,维护一下块内的点通过边能到达的编号最小的块内的点,设为\(b_i\)。。
因为\(a_i\)只会不断变小,而且当\(a_i\)小于这个块的左端点之后,这个块内会满足\(b_i=i\)。
这样子的话,修改的方式就很明朗了。
两边的块直接暴力,中间的块看一下是不是所有点都满足\(b_i=i\),如果是,打个标记就行,如果不是就暴力。
两边的块的效率肯定是根号的,打标记肯能保证根号,块内暴力的部分每个点最多减根号次(超过根号次肯定\(b_i=i\)了)。
询问也很直接,假设询问的点是\(u,v(u<v)\)。
如果两个点不在同一个块内,靠后的点\(v\)一定跳到\(fa(b_v)\)。
否则在同一块内,如果\(b_u=b_v\),说明lca就在这个块内,接下来暴力跳是根号的。
再然后如果\(b_ub_v\)不相等,两个点都跳出这个块。
每次询问都最多跨越根号个块,并且只会在某个块内暴力跳,都是根号的。
效率就是\(O((n+Q)sqrt(n))\)
\(Code\)
#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
const LL P=998244353;
const int N=3e5+10;
const int INF=1e9;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void print(LL x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
void pls(LL &x,LL y){
x+=y;if(x>=P)x-=P;
}
const int E=320;
int n,Q;
int a[N],g[N],tag[N],mx[N],b[N];
void init(int i){
int L=(i-1)*E+1,R=min(n,i*E);
mx[i]=0;
for(int j=L;j<=R;++j){
if(a[j]>mx[i]) mx[i]=a[j];
if(a[j]<L) b[j]=j;
else b[j]=b[a[j]];
}
}
void MAIN(){
int L,R;
n=read();Q=read();
for(int i=2;i<=n;++i) a[i]=read();
a[1]=1;
for(int i=1;i<=n;++i) g[i]=(i+E-1)/E;
for(int i=1;i<=g[n];++i) {
tag[i]=0;
init(i);
}
int op,l,r,x;
while(Q--){
op=read();l=read();r=read();
if(op==1){
x=read();
if(g[l]==g[r]){
for(int i=l;i<=r;++i){
a[i]=max(a[i]-x,1);
}
if(mx[g[l]]>(g[l]-1)*E) init(g[l]);
}
else{
for(int i=l;i<=g[l]*E;++i){
a[i]=max(a[i]-x,1);
}
if(mx[g[l]]>(g[l]-1)*E) init(g[l]);
for(int i=(g[r]-1)*E+1;i<=r;++i){
a[i]=max(a[i]-x,1);
}
if(mx[g[r]]>(g[r]-1)*E) init(g[r]);
for(int i=g[l]+1;i<g[r];++i){
if(mx[i]<(i-1)*E) tag[i]+=x;
else{
for(int j=(i-1)*E+1;j<=i*E;++j){
a[j]=max(a[j]-x,1);
}
init(i);
}
}
}
}
else{
while(1){
if(l==r) break;
if(l>r) swap(l,r);
if(g[l]!=g[r]){
r=max(a[b[r]]-tag[g[r]],1);
continue;
}
if(b[l]!=b[r]){
l=max(a[b[l]]-tag[g[l]],1);
r=max(a[b[r]]-tag[g[r]],1);
continue;
}
r=max(a[r]-tag[g[r]],1);
}
printf("%d\n",l);
}
}
return;
}
int main(){
int ttt=1;
while(ttt--) MAIN();
return 0;
}
I Ruler Of The Zoo
过于毒瘤,不补这题了QAQ