Codeforces Global Round 10 / contest 1392



题目地址:https://codeforces.com/contest/1392



A Omkar and Password

题意:给你一个数组,每次你可以选择相邻的两个不相同的数,然后把这两个数替换为他们的和(数量减一),问最后数组长度最小是多少。

思路:如果全部数都相同,那么长度为原来长度,否则为 1,因为一定可以找到最大的并且相邻有不同的数,然后一直用这个数就可以处理下去。

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
int a[maxn];

int main()
{
    int T=read();
    while(T--)
    {
        int n=read();
        REP(i,1,n) a[i]=read();
        int maxx=0,sum=0;
        REP(i,1,n) maxx=max(maxx,a[i]);
        REP(i,1,n) sum+=a[i]==maxx;
        printf("%d\n",sum==n?n:1);
    }

    return 0;
}



B Omkar and Infinity Clock

题意:有一个数组,每次操作选择其中最大的数 d,然后把每个位置都替换成 d-a[i],现在让你操作 k 次,问最后数组是怎样的。

思路:找到规律,第一次操作之后会出现 0,然后之后每两次操作都是一个循环。

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
int a[maxn];

int main()
{
    int T=read();
    while(T--)
    {
        int n=read();
        LL k;
        scanf("%lld",&k);
        REP(i,1,n) a[i]=read();
        k--;
        int maxx=*max_element(a+1,a+n+1);
        REP(i,1,n) a[i]=maxx-a[i];
        if(k%2==1)
        {
            maxx=*max_element(a+1,a+n+1);
            REP(i,1,n) a[i]=maxx-a[i];
        }
        REP(i,1,n) printf("%d ",a[i]);
        puts("");
    }

    return 0;
}



C Omkar and Waterslide

题意:有一个数组,每次操作可以选择其中一段单调不减的区间,然后给每个数 +1,问最少操作多少次可以让整个数组单调不减。

思路:遍历,有 a i < a i − 1 a_i<a_{i-1} ai<ai1 的,就把答案加上他们的差。

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
int a[maxn];

int main()
{
    int T=read();
    while(T--)
    {
        int n=read();
        REP(i,1,n) a[i]=read();
        LL ans=0;
        REP(i,2,n) if(a[i]<a[i-1]) ans+=a[i-1]-a[i];
        printf("%lld\n",ans);
    }

    return 0;
}



D Omkar and Bed Wars

题意:有 n 个选手围成一圈,每个选手有一个攻击方向(向左或向右),现在规定比赛要求:

  • 如果一个选手被一个人攻击,那么这个选手必须攻击那个人;
  • 否则,这个选手可以随意攻击任何相邻的人;

现在给出初始状态,问最少需要改变几个人的状态,才能使所有人满足要求。

思路:对于每个人,实际上只用考虑它左右两个人的状态就可以了,然后发现,只有连续三个相同方向这种情况是不符合要求的。所以目标就是考虑一个环,要改变最少的状态,使得不存在三个连续相同的状态。我们就找出每一段连续的相同的区间(假设长度为 len),然后改变 ⌊ l e n 3 ⌋ \lfloor\frac{len}{3}\rfloor 3len 个就行了。

还要特别注意初始全部相同的情况(答案是 ⌈ n 3 ⌉ \lceil\frac{n}{3}\rceil 3n )!

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
char s[maxn];

int main()
{
    int T=read();
    while(T--)
    {
        int n=read();
        scanf("%s",s+1);
        int ans=0,l=1,r=n,tot=1;
        while(l+1<=r && s[l+1]==s[l]) tot++,l++;
        while(r>l && s[r]==s[1]) tot++,r--;
        if(tot==n) ans+=tot%3==0?tot/3:tot/3+1;
        else ans+=tot/3;
        l++;
        while(l<=r)
        {
            int ll=l;
            tot=0;
            while(l<=r && s[l]==s[ll]) l++,tot++;
            ans+=tot/3;
        }
        printf("%d\n",ans);
    }

    return 0;
}



E Omkar and Duck

题意:一道交互题。给出一个 n(不超过25),表示有一个 n×n 的地图,有一个人要从 (1, 1) 走到 (n, n),并且它只能往右或者往下走,但是你不知道他走的路线。现在你可以给每个格子赋上一个值,然后有 q 组询问,每组询问给出那个人走过的路径上的值之和,你需要输出它走过的路径。

思路:看到这个肯定能想到二进制,但是要怎么构造地图呢?我们把同一行的格子从前往后,相邻的相差乘积为 2,然后把相邻行的初始格子的值相差乘积为 4,这样根据给出的和的二进制表示,如果连续的一段 1,那么说明它走在同一行,如果相隔了 0,那么说明它往下走了一行。这里还可以再优化,我们可以相隔地令其中的一些行全部是 0,这样在刚才的基础上,跟距相隔了几个 0,可以判断它在中间全 0 行走了多少步。

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int main()
{
    int n=read();
    REP(i,1,n)
    {
        if(i%2==1)
        {
            REP(j,1,n) printf("0 ");
            puts("");
        }
        else
        {
            REP(j,1,n) printf("%lld ",1ll<<(i-1+j-1));
            puts("");
        }
    }
    fflush(stdout);
    int q=read();
    while(q--)
    {
        LL k;
        scanf("%lld",&k);
        int l,r=0,L[50],R[50],len=0;
        while(r<52)
        {
            while(r<52 && ((1ll<<r)&k)==0) r++;
            if(r>=52) break;
            l=r;
            while(r<52 && ((1ll<<r)&k)) r++;
            len+=2;
            L[len]=l-(len-2);
            R[len]=r-1-(len-2);
        }
        L[0]=R[0]=1;
        L[n+1]=R[n+1]=n;
        for(int i=1;i<=n;i++)
        {
            if(i%2==1)
            {
                for(int j=R[i-1];j<=L[i+1];j++)
                    printf("%d %d\n",i,j);
            }
            else
            {
                for(int j=L[i];j<=R[i];j++)
                    printf("%d %d\n",i,j);
            }
        }
        fflush(stdout);
    }

    return 0;
}



F Omkar and Landslide

题意:给出一个严格单调递增的数组,如果相邻两个元素满足 a i < a i + 1 + 1 a_i<a_{i+1}+1 ai<ai+1+1 ,那么就会发生“雪崩”,也就是会令 a i a_i ai 加一,令 a i + 1 a_{i+1} ai+1 减一。对于数组整体这些动作是同时完成的。现在问最末状态时怎样的。

思路:最终的稳定状态一定是相邻相差 1,然后最多只有一组相邻的是相等的值。前面的那个很好理解,后面的这个是因为,假设有两组相邻的是相等的,那么我们反推回去,就会发现这个数组不满足严格单调递增的条件。比如对于 2 2 3 4 5 5 这个状态,它反推会经历这样的过程:1 3 3 4 5 51 2 4 4 5 51 2 3 5 5 51 2 3 4 6 5 ,就不成立。

所以有了最终状态之后,我们一开始给所有元素求个和,然后二分出起始数字,然后把剩下的给前缀就行了。

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int main()
{
    LL n=read();
    LL s=0,k;
    REP(i,1,n)
    {
        scanf("%lld",&k);
        s+=k;
    }
    LL l=0,r=1e12+1,mid;
    while(l<r-1)
    {
        mid=(l+r)>>1;
        if(mid*n+n*(n-1)/2<=s) l=mid;
        else r=mid;
    }
    LL t=l*n+n*(n-1)/2;
    REP(i,1,n)
    {
        if(i<=s-t) printf("%lld ",l+i);
        else printf("%lld ",l+i-1);
    }

    return 0;
}


G Omkar and Pies

题意:制作派的工具有 k( k ≤ 20 k\le 20 k20) 个槽位,每个槽位可以放 0 和 1 两种派。现在有 n( n ≤ 1 e 6 n\le 1e6 n1e6) 个精灵排成一排,每个精灵可以把第 a i a_i ai 和第 b i b_i bi 个槽位的派交换一次。现在给出初始 01 排列和期望排列,你需要选择一段长度不小于 m 的精灵序列,然后让这一段精灵对初始排列进行操作,并最大化和期望排列相同的数的个数。

思路:假设选择的交换序列为 [ l , r ] [l,r] [l,r] ,我们考虑对初始排列和期望排列共同操作,这是不改变相同数的个数的。我们对两个排列同时从 r 往 1 进行交换操作,最后实际上等价于对于初始排列进行 [ 1 , l − 1 ] [1,l-1] [1,l1] 的反向交换,对于期望排列进行 [ 1 , r ] [1,r] [1,r] 的反向操作。我们定义 s i s_i si 表示对初始排列进行 [ 1 , i ] [1,i] [1,i] 反向交换后的字符串, t i t_i ti 对应期望排列的交换后的字符串(这两个都很好算出来),那么原问题变成:找到 i i i j j j j − i ≥ m j-i\ge m jim,使得 s i s_i si t j t_j tj 相同的数的个数最多。

我们假设两个 01 字符串相同的 1 的个数为 x,那么它们总共相同的个数就是 n + 2 x − s 1 − t 1 n+2x-s1-t1 n+2xs1t1 ,其中 s1 和 t1 分别表示两种排列的 1 的个数,这个可以从反向很容易推出来。所以目标就是最大化 x 。设 l e f t [ i ] left[i] left[i] 表示 s 中以 i 的 1 为子集的字符串(集合)的最左的位置, r i g h t [ i ] right[i] right[i] 表示 t 中以 i 的 1 为子集的字符串(集合)的最右的位置,这两个数组可以通过状压dp算出来(这其实还是第一次这样算,就是先根据 s 和 t 不考虑子集计算一次,然后从后往前,对于每个集合,枚举每个位置,然后对它删去一个元素的集合进行更新,非常神奇)。最后枚举每一种状态,根据 m 的条件更新答案即可。

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int get(string s)
{
    int ret=0;
    REP(i,0,s.size()-1)
    {
        if(s[i]=='1') ret|=(1<<i);
    }
    return ret;
}

const int maxn=2e6+6;
int n,m,k,a[maxn],b[maxn],p[30];
string ss,tt,s[maxn],t[maxn];
int L[maxn],R[maxn];

int main()
{
    n=read(),m=read(),k=read();
    cin>>ss>>tt;
    s[0]=ss; t[0]=tt;
    REP(i,1,n) a[i]=read()-1,b[i]=read()-1;
    REP(i,0,k) p[i]=i;
    for(int i=0;i<(1<<k);i++) L[i]=1e9,R[i]=-1e9;
    REP(i,1,n)
    {
        s[i]=t[i]=string(k,'0');
        swap(p[a[i]],p[b[i]]);
        for(int j=0;j<k;j++)
        {
            s[i][p[j]]=ss[j];
            t[i][p[j]]=tt[j];
        }
    }
    //REP(i,1,n) cout<<s[i]<<endl;
    REP(i,0,n)
    {
        L[get(s[i])]=min(L[get(s[i])],i);
        R[get(t[i])]=max(R[get(t[i])],i);
    }
    for(int i=(1<<k)-1;i>=0;i--)
    {
        for(int j=0;j<k;j++) if((1<<j)&i)
        {
            L[i^(1<<j)]=min(L[i^(1<<j)],L[i]);
            R[i^(1<<j)]=max(R[i^(1<<j)],R[i]);
        }
    }
    int ans=0,l=1,r=1;
    for(int i=0;i<(1<<k);i++)
    {
        if(R[i]-L[i]>=m)
        {
            if(__builtin_popcount(i)>ans)
            {
                ans=__builtin_popcount(i);
                l=L[i]+1; r=R[i];
            }
        }
    }
    printf("%d \n%d %d",k+2*ans-count(ss.begin(),ss.end(),'1')-count(tt.begin(),tt.end(),'1'),l,r);

    return 0;
}


H ZS Shuffles Cards

题意:有一堆卡片,分为带数字的(1-n)和特殊牌(总共 m 张),还有一个数字集合(一开始为空集),现在把卡片随机放成一堆,然后每次进行如下操作:

拿出最上面的那张卡片,如果:

  • 卡片是带数字的,那么把这个数字放进集合;
  • 卡片是特殊牌,那么把所有卡片再次重新打乱,放成一堆;然后再检验数字集合是否包含了 1-n,如果包含了全部,游戏结束;

现在给出 n 和 m,问游戏操作次数期望。

思路:E(游戏操作次数) = E(游戏轮数) × E(每一轮次数) 。先考虑每一轮游戏操作次数的期望,因为如果选择了一张特殊牌,这一轮就会结束,那么我们只需要算出把所有牌排列,最前面带数字的牌的张数期望,然后 +1 就行了,考虑一开始摆好了所有特殊牌,对于每张数字牌,它在所有特殊牌前面的概率是 1 m + 1 \frac{1}{m+1} m+11 ,所以这一部分的操作次数期望就是 ( n m + 1 + 1 ) (\frac{n}{m+1}+1) (m+1n+1) ;然后考虑游戏轮数期望,设 I k I_k Ik 表示还剩下 k 个数字没有进入集合时,剩余操作轮数的期望,那么对于当前这一轮,我选择到有用的数字牌的概率是 k m + k \frac{k}{m+k} m+kk ,选择到特殊牌的概率是 m m + k \frac{m}{m+k} m+km ,而选择到有用的数字牌,意味着进入了 I k − 1 I_{k-1} Ik1 ,并且轮数没有增加,如果选择到了特殊牌,意味着还是停留在 I k I_k Ik ,而且增加了一轮,这就构成了一个递归的式子: I k = k m + k I k − 1 + m m + k ( I k + 1 ) I_k=\frac{k}{m+k}I_{k-1}+\frac{m}{m+k}(I_k+1) Ik=m+kkIk1+m+km(Ik+1) ,化简可以得到 I k = ( m ∑ i = 1 k 1 i + 1 ) I_k=(m\sum\limits_{i=1}^{k}\frac{1}{i}+1) Ik=(mi=1ki1+1)

所以最后的答案就是 ( n m + 1 + 1 ) ( m ∑ i = 1 n 1 i + 1 ) (\frac{n}{m+1}+1)(m\sum\limits_{i=1}^{n}\frac{1}{i}+1) (m+1n+1)(mi=1ni1+1)

代码

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int M=998244353;
int ksm(int x,int k)
{
    int ret=1;
    while(k)
    {
        if(k&1) ret=1ll*ret*x%M;
        x=1ll*x*x%M;
        k>>=1;
    }
    return ret;
}

int main()
{
    int n=read(),m=read();
    int ans1=(1ll*n*ksm(m+1,M-2)%M+1)%M;
    int ans2=0;
    REP(i,1,n) ans2=(ans2+ksm(i,M-2))%M;
    ans2=(1ll*ans2*m+1)%M;
    printf("%d\n",1ll*ans1*ans2%M);

    return 0;
}


I

题意

思路

代码


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值