2020CCPC绵阳分站赛部分题解

心得

感觉是超常发挥了,开局觉得D题题目短,直接开题,口胡一番发现是个二分,19分钟两发A了。之后开了K题,又是和队友一顿口胡讨论,WA了两发终于找到所有情况,73分钟A掉。看榜发现J过的人挺多,直接开题,一顿瞎搞140分钟A掉,之后两个小时就和G题杠上了,一直到最后半小时才A掉,做了四个题,L题再看的时候已经没脑子了,最后二十分钟全程看榜,看着自己最后一分钟掉出银尾,变成铜首(心态崩了)。蒟蒻只写了5个题解。

Defuse the Bombs

题意

n n n 个炸弹,每个炸弹有一个倒计时 A i A_i Ai ,倒计时小于 0 0 0 炸弹就会爆炸,现在顺序做如下操作:

  • 把一个炸弹的时间加 1 1 1
  • 所有炸弹时间减 1 1 1
  • 如果没有炸弹爆炸,重复上诉操作。

问最多能做多少次第一个操作。

1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105

分析

答案具有单调性,直接二分答案。如果能做 x x x 次操作,那么所有炸弹在 x − 1 x-1 x1 轮及之前都不会爆炸,那么如果我们操作 x − 1 x-1 x1 次能保证所有数字都不小于 0 0 0 即可。注意一开始就有两个倒计时是 0 0 0 的情况,只能操作一轮。

代码
#include <bits/stdc++.h>

using namespace std;

const int MAXN = 1e5 + 10;
long long arr[MAXN];
int n;

bool check(long long x) {
    long long sum = 0;
    for (int i = 0; i < n; i++) {
        if (arr[i] < x - 1)sum += 0 - (arr[i] - x + 1);
        //如果当前炸弹经过x-1轮会爆炸,就操作它,次数就是这个差值
        if (sum > x - 1)return false;
        //如果操作不过来了就不行
    }
    return true;
}

int main() {
    int t, T = 1;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        for (int i = 0; i < n; i++)scanf("%lld", arr + i);
        printf("Case #%d: ", T++);
        long long l = 0, r = 1e18, res = 1;
        while (l <= r) {
            long long mid = l + r >> 1;
            if (check(mid))res = mid, l = mid + 1;
            else r = mid - 1;
        }
        printf("%lld\n", res);
    }
    return 0;
}

Knowledge is Power

题意

给出一个正整数 n n n ,要求把这个数字拆成若干个两两互质的数(都要大于1),使这些数字和等于 n n n ,现在求这些数字最大值和最小值的差的最小值是多少。

5 ≤ n ≤ 1 0 9 5\leq n \leq10^9 5n109

分析
  • 如果是 6 6 6 ,则无解,输出 − 1 -1 1
  • 如果 n n n 是奇数,则可以分成 n / 2 n/2 n/2 n / 2 + 1 n/2+1 n/2+1 ,必互质,答案是 1 1 1
  • 如果 n / 2 n/2 n/2 扔是偶数,则可以分成 n / 2 − 1 n/2-1 n/21 n / 2 + 1 n/2+1 n/2+1 ,必互质,答案是 2 2 2
  • 如果 n / 2 n/2 n/2 是奇数,则分如下情况:
    • 如果 n % 3 = = 0 n\%3==0 n%3==0 则可以分成 n / 3 − 1 n/3-1 n/31 n / 3 n/3 n/3 n / 3 + 1 n/3+1 n/3+1 ,因为不可能有两个偶数,必互质,答案是 3 3 3
    • 如果 n % 3 = = 1 n\%3==1 n%3==1 则可以分成 ( n − 1 ) / 3 − 1 (n-1)/3-1 (n1)/31 ( n − 1 ) / 3 (n-1)/3 (n1)/3 ( n − 1 ) / 3 + 2 (n-1)/3+2 (n1)/3+2 ,如果互质,答案为 3 3 3 ,否则为 4 4 4
    • 如果 n % 3 = = 2 n\%3==2 n%3==2 则可以分成 ( n + 1 ) / 3 − 2 (n+1)/3-2 (n+1)/32 ( n + 1 ) / 3 (n+1)/3 (n+1)/3 ( n + 1 ) / 3 + 1 (n+1)/3+1 (n+1)/3+1 ,如果互质,答案为 3 3 3 ,否则为 4 4 4
代码
#include<bits/stdc++.h>

using namespace std;

int main() {
    int t, T = 1;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        printf("Case #%d: ", T++);
        if (n & 1) printf("1\n");
        else if (n % 4 == 0) printf("2\n");
        else if (n == 6) printf("-1\n");
        else {
            if (n % 3 == 0) {
                printf("2\n");
            } else if (n % 3 == 1) {
                int a = (n - 1) / 3;
                int b = a - 1, c = a + 2;
                if ( __gcd(b, c) == 1) printf("3\n");
                else printf("4\n");
            } else if (n % 3 == 2) {
                int a = (n + 1) / 3;
                int b = a - 2, c = a + 1;
                if (__gcd(b, c) == 1) printf("3\n");
                else printf("4\n");
            }
        }
    }
    return 0;
}

Game of Cards

题意

现在有点数分别为 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3 的四种卡片,每一种分别有 C 0 , C 1 , C 2 , C 3 C_0,C_1,C_2,C_3 C0,C1,C2,C3 张,两个人轮流操作,可以选择两张牌,这两张牌的点数之和不能大于 3 3 3 ,选择之后舍弃这两张牌,用一张新牌替代,新牌的点数就是这两张牌的点数之和。谁不能操作了谁久输了,问谁会赢。

0 ≤ C 0 , C 1 , C 2 , C 3 ≤ 1 0 9 0\leq C_0,C_1,C_2,C_3\leq10^9 0C0,C1,C2,C3109

分析

手推了一个半小时才找全规律,晕了。

首先 0 0 0 点数的牌可以当成跳过当前回合,但是不全是, 3 3 3 点数的牌基本上没意义,除非有 0 0 0 1 1 1 点数的牌要不然和 1 1 1 合成 2 2 2 ,要不然和 2 2 2 合成 3 3 3

  • 如果所有的牌的张数小于 2 2 2 ,先手必输。
  • 如果只有 0 0 0 点数的牌,是偶数则先手胜。
  • 如果 0 0 0 点的牌是偶数且有其他排则对胜负没有影响,当 C 1 % 3 = = 0 C_1\%3==0 C1%3==0 或者只有 C 1 % 3 = = 1 C_1\%3==1 C1%3==1 而没有 2 2 2 时先手输。
  • 如果 0 0 0 点是奇数,当 C 1 % 3 = = 0 C_1\%3==0 C1%3==0 或者 C 1 % 3 = = 2 C_1\%3==2 C1%3==2 并且 C 2 > 1 C_2>1 C2>1 时先手胜。
代码
#include <bits/stdc++.h>
using namespace std;

int main() {
	int t,T=1;
	scanf("%d",&t);
	while(t--) {
		long long a,b,c,d;
		scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
		printf("Case #%d: ",T++);
		if(a+b+c+d<2ll) {
			printf("Horse\n");
			continue;
		}
		if(b==0&&c==0&&d==0) {
			if(a&1)printf("Horse\n");
			else printf("Rabbit\n");
			continue;
		}
		int f=1;
		if(a%2==0) {
			if(b%3==0)f=0;
			else if(b%3==1&&c==0)f=0;
		}else{
			if(b%3==0)f=1;
			else if(b%3==1&&c==0)f=1;
			else if(b%3==1&&c)f=0;
			else if(b%3==2&&c==0)f=0;
			else if(b%3==2&&c==1)f=0;
			else if(b%3==2&&c>1)f=1;
		}
		if(f)printf("Rabbit\n");
		else printf("Horse\n");
	}
	return 0;
}

Joy of Handcraft

题意

n n n 种灯泡,有 m m m 分钟的时间,每一种灯泡亮 t t t 分钟,熄 t t t 分钟,亮的时候亮度为 x x x ,问从 1 1 1 m m m 分钟,每一分钟最亮的灯泡亮度是多少。

1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105

1 ≤ t , x ≤ 1 0 5 1\leq t,x\leq10^5 1t,x105

分析

题解讲的是用线段树,比赛的时候想到了但是觉得有点难操作,于是瞎搞用优先队列搞过了,口胡了半天队友也没听懂。。。

首先对区间赋值,对于同周期的灯泡肯定只记录一个最亮的灯泡,然后用类似埃式筛的方法给每个区间起始位置赋值,最后用一个优先队列搞一遍,对于每一分钟删去熄灭的灯,知道有亮的,添加当前点有周期开始的灯,然后输出最亮的灯的亮度即可。

代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2e5 + 10;

struct node {
    int t, x, id;
    bool operator<(const node &a) const {
        return x < a.x;//优先队列里按亮度从大到小
    }
} arr[MAXN];
int brr[MAXN];//记录第二个周期当前位置会亮的最亮灯泡
int vis[MAXN];//哪些灯泡在队列里
vector<int> crr[MAXN];//记录当前时间有哪些灯泡会亮
int n, m;

int main() {
    int t, T = 1;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d", &n, &m);
        priority_queue<node> q;
        for (int i = 0; i <= m; i++)brr[i] = 0, crr[i].clear();
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &arr[i].t, &arr[i].x);
            arr[i].id = i;
            q.push(arr[i]);
            vis[i] = 1;
            if (!brr[arr[i].t << 1 | 1])brr[arr[i].t << 1 | 1] = i;
            else if (arr[brr[arr[i].t << 1 | 1]].x < arr[i].x)brr[arr[i].t << 1 | 1] = i;
            //更新当前灯泡第二个周期开始的时候最亮的是谁
        }
        for (int i = 2; i <= m; i++)
            if (brr[i])
                for (int j = i; j <= m; j += i - 1)
                    crr[j].push_back(brr[i]);//按周期存入这里
        printf("Case #%d:", T++);
        for (int i = 1; i <= m; i++) {
            while (!q.empty()) {
                node now = q.top();
                if ((i % (now.t << 1)) == 0 || (i % (now.t << 1)) > now.t)q.pop(), vis[now.id] = 0;
                else break;
                //如果队头的当前没亮就出队,否则退出循环
            }
            for (auto &it:crr[i])
                if (!vis[it])q.push(arr[it]), vis[it] = 1;
            	//当前点会亮的灯泡入队
            if (q.empty())printf(" 0");
            else printf(" %d", q.top().x);
        }
        printf("\n");
    }
    return 0;
}

Lottery

题意

给出 n n n 组数字,每组包含两个正整数 a i , x i a_i,x_i ai,xi ,表示这个数字是 2 a i 2^{a_i} 2ai ,有 x i x_i xi 个,问这些数字相加能组成多少不同的数字。

1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105

0 ≤ a i , x i ≤ 1 0 9 0\leq a_i,x_i\leq 10^9 0ai,xi109

分析

赛后才想出来。首先肯定类似于二进制,如果有 k k k 个位置可以填 1 1 1 那么不同的数字就是 2 k 2^k 2k 个。

在这个题里面,一个数字可能不是一个,我们发现 2 2 2 2 k 2^k 2k 可以换成一个 2 k + 1 2^{k+1} 2k+1 ,这个是解题关键,如果有 3 3 3 2 1 2^1 21 ,可以等价成 1 1 1 2 2 2^2 22 和一个 2 1 2^1 21 ,那么很容易想到需要这样子转换下去,直到变成 0 0 0 个,但是要注意,对于 2 2 2 2 k 2^k 2k 并不能换成 1 1 1 2 k + 1 2^{k+1} 2k+1 0 0 0 2 k 2^k 2k 。那么操作完会在 2 2 2 的每个次方的位置上有一个数字,并且不超过 2 2 2

假如 2 2 2 1 1 1 k k k 次方,每一个次方的位置上都有至少一个数字,那么至少可以表示 2 k 2^k 2k 个不同的数字,但是有些位置上的数字是 2 2 2 肯定对答案有影响。

找了一下规律发现,把一段连续的非 0 0 0 位置上的数字都减去 1 1 1 ,以二进制表示,那么这一段数字可以组合的数字就多出这个二进制表示的数字的个数。比如有一段连续的数字的个数是 1 , 2 , 1 , 1 , 2 1,2,1,1,2 1,2,1,1,2 ,可以组合的数字就是 2 5 + ( 01001 ) 2 = 32 + 17 = 49 2^5+(01001)_2=32+17=49 25+(01001)2=32+17=49 个。然后把每一个这样的段的结果乘起来,就是答案。

代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e5 + 10;
const long long mod = 1e9 + 7;

struct node {
    long long a, x;
    bool operator<(const node A) const {
        return a < A.a;//按次方排序
    }
} arr[MAXN];

int n;

int main() {
    int t, T = 1;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        for (int i = 0; i < n; i++)scanf("%d%d", &arr[i].a, &arr[i].x);
        sort(arr, arr + n);
        long long res = 1, sum = 1, add = 0, k = 0, now = 0;
        //res记录最后的结果,sum表示2^k
        //add表示额外加上的值,k表示当前次方有的数字个数,now表示当前次方
        int ind = 0;
        while (true) {
            if (ind != n && arr[ind].a == now)k += arr[ind++].x;
            //如果当前now已经到了下一个存在的次方,加上这个次方的个数
            if (ind != n && k == 0) {
                //如果不是后一个,并且当前now的位置个数为0,计算,重新记录
                sum = (sum + add) % mod;
                res = res * sum % mod;
                sum = 1, add = 0, k = arr[ind].x, now = arr[ind++].a;
            }
            if (ind == n && k == 0) {
                //如果已经是最后一个,计算后退出
                sum = (sum + add) % mod;
                res = res * sum % mod;
                break;
            }
            if (k) {
                //如果当前位置有数字,看是否能往后表示
                if ((k & 1) == 0)add = (add + sum ) % mod;
                //这里是算add,用二进制的特性。
                sum = sum * 2 % mod;
                now++;
                k = (k - 1) >> 1;
                //如果是奇数,往前移的就是 (k-1)/2 位,偶数算出来会舍弃小数,答案不会错
            }
        }
        printf("Case #%d: %lld\n",T++,res);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值