【解题报告】Codeforces Global Round 17 | A ~ E

A:Anti Light’s Cell Guessing

题意

  • Anti Light’s Cell Guessing
    n × m n\times m n×m 的格子,其中一个位置有雷,但是不知道在哪里。
    你需要选 k k k 个格子,电脑会返回这 k k k 个格子与雷的曼哈顿距离。
    求最小的 k k k,不管雷在哪里,你都能判断出。
  • 样例组数 T ≤ 1 0 4 T \le 10^4 T104
    1 ≤ n , m ≤ 1 0 9 1\le n,m\le 10^9 1n,m109

思路

  • 显然成立的条件: n = m = 1 n=m=1 n=m=1,不用选了, k = 0 k=0 k=0
    如果 min ⁡ ( n , m ) = 1 \min(n,m)=1 min(n,m)=1,那么你选一个最边上的格子,就能判断出雷在哪了
    否则,你最少需要选两个格子,比如 ( 1 , 1 ) (1,1) (1,1) ( 1 , m ) (1,m) (1,m)
    对于相同距离这个角落的格子的曼哈顿距离,雷一定在那条斜的对角线上。
    一个斜率 k = 1 k=1 k=1 的对角线与斜率 k = − 1 k=-1 k=1 的对角线的交点,就能唯一确定那个雷。

代码

  • 时间复杂度: O ( T ) O(T) O(T)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
 
int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        if(n == 1 && m == 1){
            puts("0");
        }else if(min(n,m) == 1){
            puts("1");
        }else{
            puts("2");
        }
 
    }
    return 0;
}

B:Kalindrome Array

题意

  • Kalindrome Array
    给定一个长度为 n n n 的序列 a n a_n an
    问你是否能选择一个 x x x,删除这个序列里面部分等于 x x x 的元素(也可以不删除),使得序列成为一个回文序列?
  • 1 ≤ a i ≤ n ≤ 2 ⋅ 1 0 5 1\le a_i\le n\le 2\cdot10^5 1ain2105

思路

  • 很妙的题。
    首先我们发现,如果选择了 x x x 可以让这个序列删成回文的,那么我不妨把所有的 x x x 都删除了,是等价的。
  • 但是,我不能把每种元素都试一遍,复杂度是 n 2 n^2 n2 了,我是不是只用判断几次就可以了呢?
    考虑到,如果我们考虑 a n a_n an,每次查看头和尾是否相同。如果相同,则无所谓了。
    如果不同,那么 a l a_l al a r a_r ar 我都可以尝试删除一下,看看是否能成为回文。如果都不行,自然就不行了。

代码

  • 时间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;

int aa[MAX];
int bb[MAX];
int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i = 1;i <= n;++i)scanf("%d",&aa[i]);
        
        int st = 1,ed = n;
        int may1 = 0,may2 = 0;
        for(st = 1;st <= n / 2;++st,--ed){
            if(aa[st] != aa[ed]){
                may1 = aa[st];
                may2 = aa[ed];
                break;
            }
        }
        
        if(!may1){
            puts("YES");
            continue;
        }

        int cnt = 0;

        for(int i = 1;i <= n;++i){
            if(aa[i] != may1){
                bb[++cnt] = aa[i];
            }
        }

        bool can = true;

        for(int i = 1;i <= cnt / 2;++i){
            if(bb[i] != bb[cnt - i + 1]){
                can = false;
                break;
            }
        }

        if(can){
            puts("YES");
            continue;
        }

        cnt = 0;

        for(int i = 1;i <= n;++i){
            if(aa[i] != may2){
                bb[++cnt] = aa[i];
            }
        }

        can = true;

        for(int i = 1;i <= cnt / 2;++i){
            if(bb[i] != bb[cnt - i + 1]){
                can = false;
                break;
            }
        }

        if(can){
            puts("YES");
            continue;
        }

        puts("NO");

    }
    return 0;
}

C:Keshi Is Throwing a Party

题意

  • n n n 个人,从左往右排成一排,你要选出一些人来。
    i i i 个人能被选择,满足他左边最多被选有 b i b_i bi 个人,他右边最多被选 a i a_i ai 个人
    问你,你最多能选出几个人?
  • 1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1n2105

思路

  • 又是很妙的题…
    想了很久的 d p dp dp ,怎么想都只能做成 n 2 n^2 n2 的。
    但是突然想到了二分,然后思路直接出来了。
    人数符合二分的性质,二分可以的人数,这样你就知道 a i a_i ai 的合法性判定了,他右边被选多少人是确定的。
    但是 b i b_i bi 呢?其实只要贪心,能选就行,因为选完这个人之后,他左边不会再加人了。

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;

int aa[MAX],bb[MAX];

bool check(int n,int ren){
    int zuo = 0;
    int you = ren - 1;
    for(int i = 1;i <= n;++i){
        if(bb[i] >= zuo && aa[i] >= you){
            ren--;
            zuo++;
            you--;
            if(ren == 0)return true;
        }
    }
    return false;
}

int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n;
        scanf("%d",&n);
        for(int i = 1;i <= n;++i){
            scanf("%d%d",&aa[i],&bb[i]);
        }

        int L = 1,R = n;
        while(L < R){
            int M = (L + R + 1) >> 1;
            if(check(n,M))L = M;
            else R = M - 1;
        }

        printf("%d\n",L);
    }
    return 0;
}

D:Not Quite Lee

题意

  • Not Quite Lee
    序列 b [ 1 , 2 , ⋯   , m ] b[1,2,\cdots,m] b[1,2,,m] 是好的,如果存在 s u m i sum_i sumi 等于连续的某 b i b_i bi 个数字的和,且 ∑ s u m i = 0 \sum sum_i=0 sumi=0
    给定一个序列 a n a_n an,问你有多少个非空子序列是好的。
  • 2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le 2\cdot 10^5 2n2105
    1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109

思路

  • 赛场只能做出一半的内容,加油补题。
    首先能发现,如果子序列中有一个是奇数,一定是好的序列。偶数的貌似不好做,继续分析。
  • 我们把所有 a i a_i ai,要求长度为 a i a_i ai 的连续的数,我们先放 0 0 0,再放 1 , − 1 , 2 , − 2 , 3 , ⋯ 1,-1,2,-2,3,\cdots 1,1,2,2,3,
    这样, a i a_i ai 构成的 s u m i = a i 2 sum_i=\frac{a_i}{2} sumi=2ai
    这个序列每次可以整体向左或者向右移动一个单位,那么 s u m i = a i 2 + k i a i sum_i=\frac{a_i}{2}+k_ia_i sumi=2ai+kiai
    如果满足 ∑ s u m i = 0 \sum sum_i=0 sumi=0,容易想到必须满足
    gcd ⁡ ( a 1 , ⋯   , a m ) = g , 满 足 g ∣ ∑ a i 2 \gcd(a_1,\cdots,a_m)=g,满足 g\Big| \frac{\sum a_i}{2} gcd(a1,,am)=gg2ai
    这样,式子复杂度是 O ( 2 n log ⁡ n ) O(2^n\log n) O(2nlogn) 啊,感觉做不了,赛场我到这里就嗝屁了…但是可以继续推的!
    g ∣ ∑ a i 2   即   2 g ∣ ∑ a i   即   2 ∣ ∑ a i g   即   2 ∣ ∑ a i g g\Big| \frac{\sum a_i}{2}\\\ \\ 即\ 2g\Big|\sum a_i\\\ \\ 即\ 2\Big|\frac{\sum a_i}{g}\\\ \\ 即\ 2\Big|\sum\frac{a_i}{g} g2ai  2gai  2gai  2gai
  • 可以发现,这里是否满足,取决于 a i g \frac{a_i}{g} gai 为奇数的个数
    我们设 f ( x ) f(x) f(x) 表示 x x x 有多少个因子 2 2 2
    t t t 表示有多少个 a i a_i ai 满足 f ( a i ) = m n = min ⁡ { f ( a 1 ) , ⋯   , f ( a m ) } f(a_i)=mn=\min\{f(a_1),\cdots,f(a_m)\} f(ai)=mn=min{f(a1),,f(am)}
    我们发现, f ( x ) = m n f(x)=mn f(x)=mn,那么 x g \frac{x}{g} gx 是奇数; f ( x ) > m n f(x)>mn f(x)>mn,那么 x g \frac{x}{g} gx 是偶数
  • 那么有多少个这样的子序列是不满足要求的呢?
    即我们 t t t 中选择奇数个,这样不会让 2 ∣ ∑ a i g 2\Big|\sum\frac{a_i}{g} 2gai 满足
    方案数很好算了。 t t t 个中选择奇数个的方案为 2 t − 1 2^{t-1} 2t1,剩下的 f ( x ) > m n f(x)>mn f(x)>mn 的所有个数记为 y y y,这里的都是可选可不选,方案为 2 y 2^y 2y,两个相乘即可.
  • 我们枚举所有的 m n mn mn 即可直接计算出所有不符合要求的序列。总数减去不符合的即可。

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

int num[50];
int pre[50];
int main()
{
    int n;scanf("%d",&n);
    for(int i = 1;i <= n;++i){
        int cnt = 0;
        int t;scanf("%d",&t);
        while(t % 2 == 0){
            t /= 2;
            cnt++;
        }
        num[cnt]++;
    }
    pre[0] = num[0];
    for(int i = 1;i <= 40;++i){
        pre[i] = pre[i-1] + num[i];
    }

    ll ans = (qpow(2,n) - 1 + MOD) % MOD;

    for(int i = 1;i <= 35;++i){
        if(num[i])
            ans = (ans - qpow(2,num[i] - 1) * qpow(2,pre[40] - pre[i]) % MOD + MOD) % MOD;
    }
    printf("%lld",ans);
    return 0;
}

E:AmShZ and G.O.A.T.

题意

  • AmShZ and G.O.A.T.
    一个序列的平均值为 A V G AVG AVG,说这个序列是好的,当其中 > A V G >AVG >AVG 的元素个数小于等于其中 < A V G <AVG <AVG 的元素个数
  • 给定一个序列 a n a_n an,问你最少删除多少个元素,让剩下的序列的所有子序列都是好的
    2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le 2\cdot 10^5 2n2105
    1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109

思路

  • 从最简单的出发。假设有三个元素 a < b < c a<b<c a<b<c
    当这个序列是好的,则 A V G = a + b + c 3 ≥ b AVG=\frac{a+b+c}{3}\ge b AVG=3a+b+cb,即 c ≥ 2 b − a c\ge2b-a c2ba
    有一个奇怪的结论(?)若一个序列中任意长度为 3 3 3 的子序列都是好的,那么这个序列也是好的。
  • 我们只要算出最长好的序列即可。
    假设我们算到一半,好的序列得到了一个 [ b 1 , b 2 , ⋯   , b x ] [b_1,b_2,\cdots,b_x] [b1,b2,,bx]
    此时我们要求最优的,肯定是最小的 c c c,满足 c ≥ b x − b 1 c\ge b_x-b_1 cbxb1,因为右侧是理论最大值,必须要满足的
  • 我们可以暴力枚举好的序列的开头,每次用二分算出下一个 c c c,注意到由于 c ≥ 2 b − a c\ge 2b-a c2ba,所以复杂度总体是 log ⁡ \log log 级别的

代码

  • 时间复杂度: O ( n log ⁡ n log ⁡ { max ⁡ a i } ) O(n\log n \log \{\max a_i\}) O(nlognlog{maxai})
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

int aa[MAX];

int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i = 1;i <= n;++i)scanf("%d",&aa[i]);

        int ans = 0;
        for(int i = 1;i <= n;++i){
            if(aa[i] == aa[i-1])continue;
            int cnt = 0,pos = i;
            while(pos <= n){
                pos = lower_bound(aa + pos + 1,aa + n + 1,2 * aa[pos] - aa[i]) - aa;
                cnt++;
            }
            ans = max(ans,cnt);
        }
        printf("%d\n",n - ans);
    }
    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值