2020年CCPC秦皇岛分站赛部分题解


A Greeting from Qinhuangdao

题意

\(r\) 个红气球和 \(b\) 个蓝气球,问从这两种气球里面拿两个气球,都是红色的概率,用分数表示,如果不可能拿到两个红色气球,输出 \(0/1\)

输入

第一行一个 \(T\) 表示测试组数。

之后每一行两个数字 \(r,b\) ,表示红蓝气球个数。

\(1\leq T\leq 10\)

\(1\leq r,b\leq100\)

输出

"Case #x: y"格式输出答案,一行一个答案。

样例

输入:

3
1 1
2 1
8 8

输出:

Case #1: 0/1
Case #2: 1/3
Case #3: 7/30

分析

和名字一样友好的签到题。就是 \(C^2_r/C^2_{r+b}\) ,注意约分即可。

代码

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

int main(){
    int t,T=1;
    scanf("%d",&t);
    while(t--){
        long long n,m;
        scanf("%d%d",&n,&m);
        if(n<2){
            printf("Case #%d: 0/1\n",T++);
            continue;
        }
        long long fz=n*(n-1)/2;
        long long fm=(n+m)*(n+m-1)/2;
        long long gcd=__gcd(fz,fm);
        printf("Case #%d: %lld/%lld\n",T++,fz/gcd,fm/gcd);
    }
    return 0;
}

Good Number

题意

如果一个数字是Good Number,当且仅当 \(\lfloor \sqrt[k]x \rfloor\) 能整除 \(x\)

现在给出 \(n,k\) ,求 \(1\)\(n\) 之中Good Number 的个数。

输入

第一个一个整数 \(T\) 表示测试组数。

之后每一行两个正整数 \(n,k\)

\(1\leq T\leq 10\)

\(1\leq n,k\leq 10^9\)

输出

"Case #x: y"格式输出,一行一个答案。

样例

输入:

2
233 1
233 2

输出:

Case #1: 233
Case #2: 43

分析

分段怼就完事了。

首先对于 \(k\gt32\) 判断一下,如果成立直接输出 \(n\) ,因为 \(2^{32}\gt10^9\) ,开根号之后只能是 \(1\)

其次 \(k=1\) 判断一下。

之后记录所有 \(k\) 次方不大于 \(n\) 的数字,用两个数组记录,一个记录 \(x^k\) ,一个记录 \(x\)

完了之后做如下操作,例如 \(k=2\)\(n=20\) ,用 \(arr\) 记录 \(x\) ,用 \(brr\) 记录 \(x^k\)

对于 1~3 之间的数字开根号向下取整为1,故1~3之间的数字都是Good Number。4~8之间的数字开根号都为2,有 \(8-4=4\) 个数字(分别是 \(5,6,7,8\)) ,其中有2个数字能被2整除,而 4 本身也能被 2 整除。所以对于4~8区间内的Good Number个数为 \((8-4)/2+1\)。同理 9~15 之间的数字开根号都为 3 ,Good Number 个数为 \((15-9)/3+1\)。对于最后的 16~20 ,开根号都为4,所以最后加上 \((n-16)/4+1\)

代码

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

vector<long long>arr,brr;

long long fastpow(long long a,long long b){
    long long res=1;
    while(b){
        if(b&1)res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}

int main(){
    int t,T=1;
    scanf("%d",&t);
    while(t--){
        arr.clear();
        brr.clear();
        long long n,k;
        scanf("%lld%lld",&n,&k);
        if(k==1||k>32){
            printf("Case #%d: %lld\n",T++,n);
            continue;
        }
        long long y=1;
        while(true){
            long long x=fastpow(y,k);
            if(x>n)break;
            arr.push_back(y);//记录底数
            brr.push_back(x);//记录次方数
            y++;
        }
        int len=arr.size();
        long long res=0;
        for(int i=1;i<len;i++)
            res+=(brr[i]-1-brr[i-1])/arr[i-1]+1;
        	//上述分析的公式
        if(brr[len-1]==n)res+=1;
        //如果n是一个次方数,直接加上1
        else if(brr[len-1]<n)
            res+=(n-brr[len-1])/arr[len-1]+1;
        //上述公式
        printf("Case #%d: %lld\n",T++,res);
    }
    return 0;
}

Friendly Group

题意

\(n\) 个人, \(m\) 对关系,如果一个群体中多一对朋友关系,多一点友好值,多一个人则少一点友好值,选择若干个群体问最后友好值最多是多少。

输入

第一行一个数字 \(T\) 表示测试组数。

之后每一组第一行两个数字 \(n,m\) 表示上述 \(n,m\)

之后 \(m\) 行每行两个正整数 \(x,y\) 表示 \(x\)\(y\) 是朋友。

\(1\leq T\leq10^4\)

\(1\leq n\leq 3\times 10^5\)

\(1\leq m\leq 10^6\)

\(\sum_1^Tn\leq2\times10^6\)

输出

"Case #x: y"格式输出,一行一个答案。

样例

输入:

2
4 5
1 2
1 3
1 4
2 3
3 4
2 1
1 2

输出:

Case #1: 1
Case #2: 0

分析

并查集没跑了。

如果两个人是一个集合的,把这个集合的祖先的关系数加个1。

如果不是一个集合的,把其中一个祖先的关系数和人数加上另一个集合祖先的关系数和人数,并且加上当前的一个关系。合并。

最后循环一遍,在祖先中如果关系数大于人数则加上差值。

代码

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

const int MAXN=3e5+10;
int fa[MAXN];
int cnt[MAXN];//记录当前集合的人数
int sum[MAXN];//记录当前集合的关系数

int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

int main(){
    int t,T=1;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)fa[i]=i,sum[i]=0,cnt[i]=1;
        //每个点的人数是初始化为1
        while(m--){
            int u,v;
            scanf("%d%d",&u,&v);
            int uu=find(u),vv=find(v);
            if(uu==vv)sum[uu]++;//如果在同一个集合,关系数+1
            else {//否则转移
                sum[find(uu)]+=sum[find(vv)]+1;
                cnt[find(uu)]+=cnt[find(vv)];
                fa[uu]=vv;
            }
        }
        int res=0;
        for(int i=1;i<=n;i++)
            if(fa[i]==i)res+=max(0,sum[i]-cnt[i]);
        	//加上差值为正的差
        printf("Case #%d: %d\n",T++,res);
    }
    return 0;
}

Exam Results

题意

\(n\) 个同学参加考试,每个人可能会得到两个分数 \(a_i,b_i\) 。其中 \(a_i\ge b_i\) 如果这个同学发挥的好就是得高分\(a_i\),发挥的不好就是低分 \(b_i\)。现在考试规则如下。取所有同学考试成绩的最高分,假设为 \(x\) ,如果成绩大于 \(x\times p\%\) 则通过考试。问理论上,最多能通过考试的人数为多少人。

输入

第一行一个正整数 \(T\) 表示测试组数。

之后每组第一行两个正整数 \(n,p\)

之后 \(n\) 行每行两个正整数 \(a_i,b_i\) 表示每个同学的高分和低分。

\(1\leq T\leq 5\times10^3\)

\(1\leq n\leq 2\times 10^5\)

\(1\leq p\leq100\)

\(1\leq b_i\leq a_i\leq10^9\)

\(\sum^T_1n\leq5\times10^5\)

输出

"Case #x: y"格式输出,一行一个答案。

样例

输入:

2
2 50
2 1
5 1
5 60
8 5
9 3
14 2
10 8
7 6

输出:

Case #1: 2
Case #2: 4

分析

和经典差分例题"校门外的树"很像。

一个同学的高分 \(a_i\) 可以被取到,说明最高分 \(x\) 所在的范围为 \([a_i,a_i*100/p]\),同理低分 \(b_i\) 能被取到的范围是 \([b_i,b_i*100/p]\) ,将所有的边界值存起来离散化一下,然后差分求最大值即可。

需要注意的是,我们所取的 \(x\) 必须大于等于 \(b_i\) 的最大值。

举个例子:

4 50
8 3
9 4
84 23
2 1

如果我们的 \(x\) 取的是4 ,那么在 2~4 分之间的都可以通过,显然有三个人。

但是第三个人的成绩无论如何都比4分高,那这个人凭什么不能通过呢。如果这个人通过了那么 \(x\) 只能是第三个人的成绩。这样其他人又没法通过了。

比赛的时候我们队就犯了这个笨比错误,卡了三个小时。(暴风哭泣)

代码

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

const int MAXN=2e5+10;
long long A[MAXN],B[MAXN];
long long C[MAXN<<2],sub[MAXN<<2];
//C记录边界,sub记录差分
int n,p;

int main(){
    int t,T=1;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&p);
        for(int i=0;i<=4*n;i++)sub[i]=0;
        long long mi=0;
        for(int i=0;i<n;i++){
            scanf("%d%d",A+i,B+i);
            C[i*4]=A[i],C[i*4+1]=A[i]*100/p;
            C[i*4+2]=B[i],C[i*4+3]=B[i]*100/p;
            //存储边界
            mi=max(mi,B[i]);
            //记录B的最大值
        }
        sort(C,C+n*4);
        for(int i=0;i<n;i++){
            int al=lower_bound(C,C+4*n,A[i])-C;
            int ar=lower_bound(C,C+4*n,A[i]*100/p)-C;
            int bl=lower_bound(C,C+4*n,B[i])-C;
            int br=lower_bound(C,C+4*n,B[i]*100/p)-C;
            sub[bl]++,sub[ar+1]--;//差成绩的左边界和好成绩的右边界先处理
            if(al>br)sub[br+1]--,sub[al]++;
            //如果区间没有重合,处理一下
        }
        long long s=0;
        long long res=0;
        for(int i=0;i<4*n+1;i++) {
            s+=sub[i];
            if(C[i]>=mi)res=max(res,s);//记录大于B的最大值的覆盖区间
        }
        printf("Case #%d: %lld\n",T++,res);
    }
    return 0;
}

Kingdom's Power

题意

给一颗树,根节点有无限多的军队,每一次只能移动一只军队,问最少移动多少次可以遍历所有节点。

输入

第一行一个正整数 \(T\) 表示测试组数。

之后每组第一行两个正整数 \(n\) ,表示节点数。

之后 \(n-1\) 行,一行一个数,表示 \(2\)\(n\) 号节点的父节点是谁。

1是根节点。

\(1\leq T\leq10^5\)

\(1\leq n\leq 10^6\)

\(\sum^T_1n\leq2\times10^6\)

输出

"Case #x: y"格式输出,一行一个答案。

样例

输入:

2
3
1 1
6
1 2 3 4 4

输出:

Case #1: 2
Case #2: 6

分析

树上DP。

我们首先会去最浅的节点,然后考虑是从浅节点出来去深节点还是直接再从根节点派一只军队去深节点。

这也是之后看巨佬的题解看懂的。

代码

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

const int MAXN=1e6+10;
struct node{
    int son;
    int deep;
    bool operator<(const node a)const{
        return deep<a.deep;
    }
};
vector<node>tree[MAXN];
long long sum[MAXN];
int n;

int dfs1(int now){
    int res=0;
    for(auto &i:tree[now]){
        int son=i.son;
        int deep=dfs1(son);
        i.deep=deep;
        res=max(res,deep);
        //记录最深
    }
    sort(tree[now].begin(),tree[now].end());
    return res+1;
}

int dfs2(int now,int s,int d){
    sum[now]=s;
    if(tree[now].empty())return 1;
    //如果是根节点,往回走的时候步数从1开始计算
    for(auto &i:tree[now]){
        int son=i.son;
        s=min(d,dfs2(son,s+1,d+1));
        //s的值是看从根节点再来还是从浅节点来比较快
    }
    return s+1;
}

int main(){
    int t,T=1;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)tree[i].clear();
        for(int i=2;i<=n;i++){
            int x;
            scanf("%d",&x);
            tree[x].push_back(node{i,0});
        }
        dfs1(1);
        dfs2(1,0,0);
        long long res=0;
        for(int i=1;i<=n;i++)
            if(tree[i].empty())res+=sum[i];
        	//注意,只加根节点的值
        printf("Case #%d: %lld\n",T++,res);
    }
    return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM