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

posted @ 2021-03-02 14:54  Iscream-2001  阅读(119)  评论(0编辑  收藏  举报
/* */ /* */