CF 782

A

题意:

\(a\)个红球和\(b\)个蓝球,让你摆成一排,连续红球数量的最大值最小。

题解:

可以去二分,但没必要,数量就是\(x=\lceil\frac{a}{b+1}\rceil\)

构造就是满\(x\)个红球就放一个蓝球,直到没红球了,写的很乱就不放了。

B

题意:

给一个长度为\(n\)\(01\)串,你要做恰好\(k\)次操作,每次选一个位置,把这个位置之外的所有位置都取反。

求最后字典序最大的结果。

题解:

考虑贪心,我们先让前面的数字最大,尽量成为\(1\)

怎么做呢,比如第一位是\(1\),那么我们很想让不选位置一的操作次数是偶数,这样可以保证第一位比较大。

所以如果\(k\)是奇数,把\(k\)在这里用一次,否则不用动,把\(k\)直接留给后面去用。

然后下一位,下一位的值是本来的值加上前面使用次数的影响。

问题规模减小。最后把剩下的次数都丢给最后一位消化。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=4e5+10,mod=998244353,inf=2e9;
    int n,m,tot;
    char s[N];
    int a[N];
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        int T;cin>>T;
        while(T--)
        {
            cin>>n>>m;tot=0;
            cin>>(s+1);
            for(int i=1;i<=n;++i) a[i]=0;
            if(!m)
            {
                cout<<(s+1)<<'\n';
                for(int i=1;i<=n;++i) cout<<0<<" \n"[i==n];
                continue;
            }
            for(int i=1;i<=n;++i)
            {
                if(tot&1)
                {
                    if(s[i]=='1') s[i]='0';
                    else s[i]='1';
                }
                if(m<=0||i==n) continue;
                if(s[i]=='1')
                {
                    if(m%2==1)
                    {
                        --m;++a[i];++tot;
                    }
                }
                else
                {
                    if(m%2==0)
                    {
                        --m;++a[i];++tot;
                    }
                    s[i]='1';
                }
            }
            a[n]+=m;
            cout<<(s+1)<<'\n';
            for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
        }
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
1 2 3 4

*/

C

题意:

有个王国位于\(x_0=0\),有\(n\)个城市,坐标是\(x_1,x_2,…,x_n\),满足\(x_1<x_2<…<x_n\)

要求按顺序征服\(n\)个城市

如果当前首都是\(i\),要征服\(j\),代价是\(b*|x_i-x_j|\)

如果当前首都是\(i\),要迁都\(j\),代价是\(a*|x_i-x_j|\),但目的地必须被征服。

求征服所有城市的最小代价。

题解:

考虑如果最后最优答案的首都在\(k\),那么我们一定会刚征服\(k\)就迁都过去。

如果不搬过去,这一段路会贡献\((n-i+1)*b*|x_i-x_{i-1}|\)的代价,因为后面每座城市都要走一遍。

如果搬过去,这一段路会贡献\((a+b)*|x_i-x_{i-1}|\),先打下来,再搬过去。

枚举最终那个位置作为首都,然后取对答案\(min\)

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=4e5+10,mod=998244353,inf=2e9;
    int n,a,b,ans;
    int p[N],s[N];
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        int T;cin>>T;
        while(T--)
        {
            cin>>n>>a>>b;ans=inf*inf;
            for(int i=1;i<=n;++i)
            {
                cin>>p[i];
            }
            s[n+1]=0;
            for(int i=n;i>=1;--i) s[i]=s[i+1]+p[i];
            for(int i=0;i<=n;++i)
            {
                ans=min(ans,(a+b)*p[i]+(s[i+1]-p[i]*(n-i))*b);
            }
            cout<<ans<<'\n';
        }
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
1 2 3 4

*/

D

题意:

有一个仅有\(0/1\)构成的数组\(A\)

\(B_i\)是把\(A\)的前\(i\)位从小到大排序,其他位不动的数组

现在有数组\(C=B_1+B_2+…+B_n\)(对应位相加),还原\(A\)数组,保证有解。

题解:

倒序处理,可以发现\(A\)中的每个\(1\)都会给\(C\)中的总和\(+n\),所以最后\(A\)\(1\)的数量就是\(C\)每一位的和除以\(n\),设为\(k\)

如果某一位\(i\)前面有\(k\)\(1\),那么\([i-k+1,i]\)都会被\(+1\),我们可以倒着想这个过程。

如果某一位前面有\(k\)\(1\),我们就给\([i-k+1,i]\)都减去\(1\),如果这一位减完了之后还剩下\(i-1\),那说明这一位是\(1\),这剩下的\(i-1\)是排序排不到这个位置时作出的贡献。

对于第一位要特判,因为他前面没有人给他垫,剩下的数只能第一位拿走。

这个过程可以用树状数组模拟,也可以用差分模拟。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=4e5+10,mod=998244353,inf=2e9;
    int n,r,sum;
    int a[N],c[N],b[N];
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        int T;cin>>T;
        while(T--)
        {
            cin>>n;r=sum=0;
            for(int i=1;i<=n;++i)
            {
                cin>>c[i];
                a[i]=0;
                sum+=c[i];
                b[i]=0;
            }
            int k=sum/n;
            int l=n-k+1;
            b[n+1]=0;
            for(int i=n;i>=1&&k;--i)
            {
                ++b[i];--b[max(0ll,i-k)];
                b[i]+=b[i+1];
                c[i]-=b[i];
                if(c[i]>0)
                {
                    a[i]=1;
                    --k;
                }
                else a[i]=0;
            }
            if(k) a[1]=1;
            for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
        }
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
7
0 3 4 2 3 2 7



1 1 1 0 0 1

1 1 1 0 0 1
1 1 1 0 0 1
1 1 1 0 0 1
0 1 1 1 0 1
0 0 1 1 1 1
0 0 1 1 1 1

1
6
3 4 6 3 2 6


*/

E

题意:

给一张有\(n\)个顶点,\(m\)条边的图,每条边有边权。

定义一条路径的权值:

设这条路径的起点\(u\)到终点\(v\)中间经过的边的边权是\(w_1,w_2,w_3,w_4…\)

那么这条路径的权值是\(mex\{w_1,w_1\&w_2,w_1\&w_2\&w_3,……\}\)

其中\(mex\)是集合没有出现的最小自然数。

题解:

结论:任意路径的权值不超过\(2\)

证明:假如这个集合里有\(0\)\(1\),那么说明二进制第零位的一,从起点一直到数字变成零为止都没有消失,所以这个集合的\(mex\)一定是\(2\)

情况一:\(mex\)为零

枚举二进制下哪一位没有被干掉,把二进制下这一位是\(1\)的边连接的点用并查集合并。

复杂度\(O(30*n)\)

情况二:\(mex\)为一

说明集合中没有出现\(1\)数字就直接变成零了,我们假设某条路径最后一次出现奇数是二进制下第零位加某一位,枚举这个某一位,那么就可以得到一些联通块。

这些连通块内部可以保证第零位和某一位一直存在。

然后我们去枚举某个点有没有一条边,这条边的边权是偶数。这条偶数边可以直接把权值干成\(0\)

包含这样一个点的连通块都可以作为起点所在的连通块。因为起点在这里面可以先绕一圈,把权值绕成第零位加某一位,然后再去走这条偶数边,把权值干成\(0\),后面随便走都可以了。

情况三:\(max\)为二

剩下的。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=4e5+10,mod=998244353,inf=2e9;
    int n,m,k;
    struct node
    {
        int x,y,z;
    }a[N],q[N];
    int f[N];
    bool vis[N];
    inline int find(int k){return f[k]==k?k:f[k]=find(f[k]);}
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        cin>>n>>m;
        for(int i=1;i<=m;++i)
        {
            cin>>a[i].x>>a[i].y>>a[i].z;
        }
        cin>>k;
        for(int i=1;i<=k;++i)
        {
            cin>>q[i].x>>q[i].y;
            q[i].z=-1;
        }
        for(int b=29;b>=0;--b)
        {
            for(int i=1;i<=n;++i) f[i]=i;
            for(int i=1;i<=m;++i)
            {
                if(((a[i].z>>b)&1)==0) continue;
                int tx=find(a[i].x),ty=find(a[i].y);
                f[tx]=ty;
            }
            for(int i=1;i<=k;++i)
            {
                if(q[i].z!=-1) continue;
                if(find(q[i].x)==find(q[i].y)) q[i].z=0;
            }
        }
        for(int b=29;b>=1;--b)
        {
            for(int i=1;i<=n;++i) f[i]=i,vis[i]=0;
            for(int i=1;i<=m;++i)
            {
                int tmp=(1<<b)|1;
                if((a[i].z&tmp)!=tmp) continue;
                int tx=find(a[i].x),ty=find(a[i].y);
                f[tx]=ty;
            }
            for(int i=1;i<=m;++i)
            {
                if(a[i].z%2==1) continue;
                int tx=find(a[i].x),ty=find(a[i].y);
                vis[tx]=vis[ty]=1;
            }
            for(int i=1;i<=k;++i)
            {
                if(q[i].z!=-1) continue;
                if(vis[find(q[i].x)]) q[i].z=1;
            }
        }
        for(int i=1;i<=k;++i)
        {
            if(q[i].z==-1) q[i].z=2;
            cout<<q[i].z<<'\n';
        }
    }
}
signed main()
{
    red::main();
    return 0;
}
/*
1
1 2 3 4

*/

F

题意:

有一个\(1\sim n\)的排列,\(Alice\)可以每次必须选择两个数字,交换它们的位置。

有一颗\(n\)个节点的树,一开始有一块令牌在\(pos\)处,\(Bob\)每次必须选择一个相邻的节点,把它挪过去。

如果令牌位于\(pos\)处,则\(Alice\)选的数字不能有\(pos\)

\(Alice\)想把排列恢复升序,\(Bob\)想干扰他,\(Alice\)先手,谁会赢?

题解:

首先证明:如果树的直径超过三个节点,\(Alice\)一定能成功。

证明:

如果有超过\(2\)个数字不在自己该在的位置,那么每次操作至少能把一个数字放回位置。

那么我们现在剩下两个数字\(x,y\)互相占据了对方的位置,并且假设令牌在\(y\)

我们可以把\(x\)放到\(y\)旁边的一个节点,不断地把这两个节点挪到直径的某个端点处,并且两个点相邻。

然后就比较好构造方案了,如果直径上至少有\(4\)个点,一定能操作操作把这两个点归位,证明略。

然后考虑直径不超过\(3\)个节点的树,即菊花图。

我们不妨设菊花的中心是树根,并设\(k\)是把排列归为要花的最少次数。

在此之前,我们要把排列本就有序,或\(k=1\)且可以一次换好的情况特判掉。

可以把情况分成以下几种:

\(1.\)令牌在根,根没归位:

这样\(Bob\)一定赢,\(Bob\)可以在根和根要去的地方反复横跳。

我们称这个过程为\(opt1\)

\(2.\)令牌在根,根归位了:

如果\(k\)是奇数,\(Alice\)赢,否则\(Bob\)赢。

如果\(k\)是奇数,\(Alice\)可以刚好让令牌回根后完成最后一次交换,否则\(Alice\)要做最后一次交换时令牌可以刚好卡住他,他不得不去换别的数,而别的数还要再花一次换回来。

我们称这个过程为\(opt2\)

\(3.\)令牌在没归位的叶子,根归位了:

如果\(k\)是偶数,\(Alice\)赢,否则\(Bob\)赢。

和上面恰好相反,令牌一开始不在根,要花一步去根,所以和上面一种的奇偶性刚好相反。

我们称这个过程为\(opt3\)

\(4.\)令牌在没归位的叶子,根没归位:

如果令牌在的节点刚好是根要的数字,那\(Bob\)赢,他可以在这两个节点间反复横跳,你永远不能把根归位。

否则,只要花一步把根去归位,等于是\(opt3\)

\(5.\)令牌在归位的叶子,根归位或没归位:

如果根没归位,你也可以花一步去把根归位,还是只和\(k\)有关。

类似地证明了,这个也是\(opt3\),但是要注意如果一步就可以排好要提前特判。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=4e5+10,mod=998244353,inf=2e9;
    int n,pos,rt,s;
    vector<int> eg[N];
    int p[N];
    bool vis[N];
    int dep[N],maxn;
    inline void dfs(int now,int fa)
    {
        dep[now]=dep[fa]+1;
        if(dep[now]>dep[rt]) rt=now;
        for(int t:eg[now])
        {
            if(t==fa) continue;
            dfs(t,now);
        }
    }
    inline bool check(int x){return p[x]!=x;}
    int tt;
    inline void work()
    {
        s=0;
        for(int i=1;i<=n;++i) vis[i]=0;
        for(int i=1;i<=n;++i)
        {
            if(vis[i]) continue;
            vis[i]=1;
            int now=p[i];
            while(!vis[now])
            {
                tt=now;
                vis[now]=1;
                now=p[now];
                ++s;
            }
        }
    }
    inline void work1()
    {
        cout<<"Bob\n";
    }
    inline void work2()
    {
        if(s&1) cout<<"Alice\n";
        else cout<<"Bob\n";
    }
    inline void work3()
    {
        //cout<<"!!"<<endl;
        if(s&1) cout<<"Bob\n";
        else cout<<"Alice\n";
    }
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        int T;cin>>T;
        while(T--)
        {
            cin>>n>>pos;
            for(int i=1;i<=n;++i)
            {
                eg[i].clear();
            }
            for(int i=1;i<n;++i)
            {
                int x,y;cin>>x>>y;
                eg[x].emplace_back(y);
                eg[y].emplace_back(x);
            }
            for(int i=1;i<=n;++i)
            {
                int x;cin>>x;
                p[x]=i;
            }
            work();
            if(!s)
            {
                cout<<"Alice\n";
                continue;
            }
            if(s==1&&pos!=tt&&pos!=p[tt])
            {
                cout<<"Alice\n";
                continue;
            }
            rt=0;
            dfs(1,0);
            dfs(rt,0);
            if(dep[rt]>3)
            {
                cout<<"Alice"<<'\n';
            }
            else
            {
                for(int i=1;i<=n;++i)
                {
                    if(eg[i].size()>eg[rt].size()) rt=i;
                }
                if(rt==pos&&check(rt)) work1();
                else if(rt==pos&&!check(rt)) work2();
                else if(rt!=pos&&check(pos)&&check(rt))
                {
                    if(p[pos]==rt) work1();
                    else work3();
                }
                else if(rt!=pos&&check(pos)&&!check(rt))
                {
                    work3();
                }
                else if(rt!=pos&&!check(pos)&&check(rt))
                {

                    if(s==1) cout<<"Alice\n";
                    else work3();
                }
                else if(rt!=pos&&!check(pos)&&!check(rt))
                {
                    if(s==1) cout<<"Alice\n";
                    else work3();
                }
            }
        }
    }
}
signed main()
{
    // freopen("data.in","r",stdin);
    // freopen("red.out","w",stdout);
    red::main();
    return 0;   
}
/*
1
4 1
2 1
2 3
2 4
2 3 1 4 

*/
posted @ 2022-04-21 20:58  lovelyred  阅读(50)  评论(0)    收藏  举报