Codeforces Round #740 Div. 2 A B C D1 D2

A. Simply Strange Sort

题目链接
在这里插入图片描述
题意: 给你一个从 1 − n 1-n 1n的排列数组, n n n的长度是奇数,按照题目给定的要求对序列进行变换,求多少次变换之后能够使得数组递增。

分析: 简单的模拟题,主要是要读懂题意, f ( i ) f(i) f(i)代表如果 a i > a i + 1 a_i > a_{i+1} ai>ai+1,则两者可以交换。每一次迭代变换,第i次的迭代规则:如果 i i i是奇数,对 1 , 3 , 5 , 7 , 9 1,3,5,7,9 1,3,5,7,9…奇数的位置的数字可以进行 f ( 1 ) , f ( 3 ) f(1),f(3) f(1),f(3)…的操作。如果i是偶数,对 2 , 4 , 6 , 8 , 10 2,4,6,8,10 2,4,6,8,10…偶数的位置可以进行 f ( 2 ) , f ( 4 ) f(2),f(4) f(2),f(4)的操作。按照题意模拟。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1005;
int a[N],n;
bool check(){
    for(int i=1;i<=n;i++){
        if(a[i]==i)continue;
        return false;
    }
    return true;
}
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        int cnt=0,flag=0;
        while(1){
            if(check())break;
            flag^=1;
            int i=(flag?1:2);
            for(;i<=n-1;i+=2){
                if(a[i]>a[i+1])swap(a[i],a[i+1]);
            }
            cnt++;
        }
        printf("%d\n",cnt);
    }
    return 0;
}

B. Charmed by the Game

题目链接
在这里插入图片描述
题意: 这个题太难读了…就是说有两个人打球,玩家轮流发球,发球方赢了,就说这一方 holds;接球方赢了,就说这一方 b r e a k s breaks breaks。题目告诉我们两人的得分 a , b a,b a,b。求可能的 b r e a k s breaks breaks的次数,也就是求接球方可能的拿分次数,也就是后手赢球的可能次数。

分析: 知道了两人的得分 a , b a,b a,b,那么我们就能知道总的比赛场次是 a + b a+b a+b,先手的发球次数是 ⌈ a + b 2 ⌉ \lceil \frac{a+b}{2} \rceil 2a+b p p p,后手的发球次数是 ⌊ a + b 2 ⌋ \lfloor \frac{a+b}{2} \rfloor 2a+b q q q
我们不知道谁先发球,先假设 A l i c e Alice Alice先发球,设 x x x A l i c e Alice Alice发球的失球个数( 0 ≤ x ≤ p 0\leq x \leq p 0xp), y y y B o r y s Borys Borys的失球个数( 0 ≤ y ≤ q 0\leq y \leq q 0yq)。
如果 A l i c e Alice Alice先手发球,枚举 A l i c e Alice Alice先手发球的失球个数 x x x(也就是后手赢球)从 1 1 1 p p p,因为有 a = ( p − x ) + y a=(p-x)+y a=(px)+y,所以 y = a − ( p − x ) y=a-(p-x) y=a(px),只要( 0 ≤ y ≤ q 0\leq y \leq q 0yq),这就是一种合法的两者比赛的结果策略。 k k k的值就是 x + y x+y x+y
同理,如果 B o r y s Borys Borys先手发球,枚举 B o r y s Borys Borys先手发球的失球个数 y y y(也就是后手赢球)从 1 1 1 p p p,因为有 b = ( p − y ) + x b=(p-y)+x b=(py)+x,所以 x = b − ( p − y ) x=b-(p-y) x=b(py),只要( 0 ≤ x ≤ q 0\leq x \leq q 0xq),这就是一种合法的两者比赛的结果策略。 k k k的值就是 x + y x+y x+y
计算上述有多少个 k k k的值。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;

int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int a,b;
        scanf("%d %d",&a,&b);
        int p=a+b+1>>1,q=a+b>>1;//先手发球的次数和后手发球的次数
        set<int>ans;
        for(int i=0;i<=p;i++){//枚举alice先手时,每一种状态
            int x=i,y=a-(p-x);//x是alice先手输球也就是break,y是Borys先手输球
            if(y>=0&&y<=q)ans.insert(x+y);
        }
        for(int i=0;i<=p;i++){//枚举Broys先手输球
            int y=i,x=b-(p-y);
            if(x>=0&&x<=q)ans.insert(x+y);
        }
        printf("%d\n",ans.size());
        for(auto x:ans)printf("%d ",x);
        printf("\n");
    }
    return 0;
}

C. Deep Down Below

题目链接
在这里插入图片描述
题意: 玩家控制一个英雄,英雄有一个力量值。英雄要进入n个洞穴,洞穴i有k_i个怪物 a i , 1 , a i , 2 , . . . a i , k a_{i,1},a_{i,2},...a_{i,k} ai,1,ai,2,...ai,k,只要进入了一个洞穴,就必须按照顺序一一打败他们。英雄能够打败他们只有英雄的力量值严格大于怪物的护甲值,如果打不过则游戏失败。英雄每打败一个怪物,英雄的力量值+1。求最小的护甲值,使得英雄可以按照一定顺序进入洞穴,并打败所有怪物。

分析: 贪心。首先,我们需要确定进入一个洞穴时,英雄最低需要多少的力量值。
因为英雄每打败一个怪物,英雄的护甲值 + 1 +1 +1。假设进入第一个洞穴英雄的力量值为 x x x,那么打完洞穴的第一个怪物,英雄的力量值变成 x + 1 x+1 x+1,打完第二个怪物变成 x + 2 x+2 x+2。所以只要保证对于第i个怪物的护甲值 y i y_i yi x + i − 1 > y i x+i-1>y_i x+i1>yi x > y i − i + 1 x>y_i-i+1 x>yii+1。求出来该洞穴的 y i − i + 1 y_i-i+1 yii+1的最大值,就是要进入该洞穴的英雄至少需要的力量值。
明确了进入一个洞穴时,英雄最低需要多少的力量值,可以对每一个洞穴按照最低需要多少力量值进行排序,优先进入洞穴所需最低力量值小的洞穴,进入洞穴的顺序就定下来了。
因为每次打完怪物英雄的力量值会增加,所以我们自然而然地想到这样的贪心策略。优先攻击进入洞穴所需最低力量值小的洞穴,打完该洞穴后力量值会增加这个洞穴的怪物数量的值。我们可以二分最低力量值,判断哪个力量值是符合条件的。
也可以不使用二分,维护一个当前英雄力量值 s u m sum sum;走到当前洞穴之前,打败的怪物数量 c n t cnt cnt;英雄最低的过关力量值 a n s ans ans
面对每一个洞穴,如果当前力量值大于等于进入洞穴所需的最低力量值,那么我们直接进入,并且更新sum为sum加上当前洞穴的怪物数量,然后更新 c n t cnt cnt
如果当前力量值比进入洞穴所需最低力量值还要低,那么更新sum为进入当前洞穴所需的最低力量值+当前洞穴的怪物数量(因为打完这个洞穴之后的力量值至少是这个),更新ans为进入当前洞穴所需的最低力量值-cnt(因为进入这个洞穴之前,我们打败了 c n t cnt cnt个怪物,加了 c n t cnt cnt的力量值,因为当前洞穴所需的最低力量值是大于之前的力量值的,所以 a n s − c n t ans-cnt anscnt在之前的洞穴肯定也都满足)。

代码:
使用二分

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
using namespace std;
const int N=1e5+5;
int n;
struct node{
    int maxx,num;
    bool operator <(const node &a) const{
        return maxx<a.maxx;
    }
}q[N];
bool check(int x){
    for(int i=1;i<=n;i++){
        int maxx=q[i].maxx,num=q[i].num;
        if(x>=maxx){
            x+=num;
            continue;
        }
        else return 0;
    }
    return 1;
}
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int k;scanf("%d",&k);
            int maxx=0;
            for(int j=1;j<=k;j++){
                int x;scanf("%d",&x);
                maxx=max(maxx,x-j+1);
            }
            q[i]={maxx+1,k};
        }
        sort(q+1,q+1+n);
        int l=1,r=1e9+5,ans=1e9+5;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid))ans=mid,r=mid-1;
            else l=mid+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

不用二分

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
using namespace std;
const int N=1e5+5;
struct node{
    int maxx,num;
    bool operator <(const node &a) const{
        return maxx<a.maxx;
    }
}q[N];
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int n;scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int k;scanf("%d",&k);
            int maxx=0;
            for(int j=1;j<=k;j++){
                int x;scanf("%d",&x);
                maxx=max(maxx,x-j+1);
            }
            q[i]={maxx+1,k};
        }
        sort(q+1,q+1+n);
        ll sum=0,ans=0,cnt=0;
        for(int i=1;i<=n;i++){
            int maxx=q[i].maxx,num=q[i].num;
            if(sum>=maxx)sum+=num;
            else sum=maxx+num,ans=maxx-cnt;
            cnt+=num;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

D1. Up the Strip (simplified version)

题目链接
在这里插入图片描述
题意: 有一个长度为 n n n的垂直条,有一个令牌最初在 n n n的位置,你需要将它向上移动,最后移动到 1 1 1的位置。令牌在 x ( x > 1 ) x(x >1) x(x>1)的位置时,有两种移位方式:

  • 1 1 1 x − 1 x−1 x1之间选择一个整数 y y y,并将令牌从单元 x x x移动到单元 x − y x−y xy
  • 2 2 2 x x x之间选择一个整数 z z z,包括 x x x,然后把这个值从单元格 x x x移动到数组元素的 ⌊ x z ⌋ \lfloor \frac{x}{z} \rfloor zx位置。

分析: 很明显的 d p dp dp问题。第一种移位方式很好考虑,维护一个后缀和 s u f suf suf,记录后缀的所有方案数量。
主要是第二种移位方式如何考虑,如果暴力枚举,肯定会超时。这个时候我们用到整除分块(不了解的可以查一查), ⌊ x z ⌋ \lfloor \frac{x}{z} \rfloor zx有很多的z可能除完之后都是到达同一个点,而且这些z是连续的。这个时候整除分块可以快速求出到达同一个点的 z z z的范围,可以很大程度上减少我们的时间复杂度。
时间复杂度 O ( n n ) O(n\sqrt n) O(nn )

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
using namespace std;
const int N=2e5+5;
ll dp[N];
int main()
{
    int n,mod;scanf("%d %d",&n,&mod);
    ll suf=0;
    dp[n]=1;
    for(int i=n;i>=1;i--){
        dp[i]=(dp[i]+suf)%mod;
        int k=2;
        while(k<=i){
            int m=i/(i/k);
            dp[i/k]=(dp[i/k]+dp[i]*(m-k+1))%mod;
            k=m+1;
        }
        suf=(suf+dp[i])%mod;
    }
    printf("%lld\n",dp[1]);
    return 0;
}

D2. Up the Strip

题目链接
在这里插入图片描述
题意: 和D1题意一致,只是数据范围变大了

分析: D1的做法时间复杂度为 O ( n n ) O(n\sqrt n) O(nn ),在这道题的数据下是肯定会 T L E TLE TLE的,所以我们需要用别的方法来优化,考虑到每一个状态由后继状态转移过来,比如说对于数x,可以由 2 x 2x 2x, 2 x + 1 2x+1 2x+1除以 2 2 2到达 x x x。对于每一个 x x x的除数 y y y,都能通过 [ x ∗ y , x ∗ y + y − 1 ] [x*y,x*y+y-1] [xy,xy+y1]的数转移过来。所以我们每次枚举倍数,维护一个后缀和,时间复杂度减小为 O ( n l o g n ) O(nlogn) O(nlogn)

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
using namespace std;
const int N=4e6+5;
ll dp[N],sum[N];
int main()
{
    int n,mod;scanf("%d %d",&n,&mod);
    dp[n]=sum[n]=1;
    for(int i=n-1;i>=1;i--){
        dp[i]=sum[i+1];//能够使用法一跳过来
        for(int j=2;j*i<=n;j++){
            int r=min((ll)n,(ll)i*j+j-1);//所有i*j+j-1都是除以j等于i的数
            dp[i]=(dp[i]+sum[i*j]-sum[r+1])%mod;
        }
        sum[i]=(sum[i+1]+dp[i])%mod;
    }
    printf("%lld\n",dp[1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a碟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值