1. 程式人生 > 實用技巧 >2020年CCPC秦皇島分站賽部分題解

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~9區間內的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;
}