1. 程式人生 > 實用技巧 >2020 CCPC秦皇島 正式賽題解

2020 CCPC秦皇島 正式賽題解

A Greeting from Qinhuangdao 概率

題意:$r$個紅球,$b$個藍球,不放回摸兩次均為紅球的概率。

思路:基礎概率論,注意一下概率為0的情況和分子分母的約分。

程式碼:

 1 #include <bits/stdc++.h>
 2 
 3 #define debug(...)  fprintf(stderr,__VA_ARGS__)
 4 #define DEBUG printf("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
 5 #define Debug debug("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
 6
#define rep(i,x,y) for(int i=(x);i<=(y);++i) 7 #define _max(a,b) (a)>(b)? (a):(b) 8 #define _min(a,b) (a)<(b)? (a):(b) 9 10 using namespace std; 11 typedef long long ll; 12 typedef unsigned long long ull; 13 typedef pair<int,int> pii; 14 typedef pair<ll,int> pli; 15 typedef pair<int
,ll> pil; 16 typedef pair<ll,ll> pll; 17 18 int t; 19 int r,b; 20 int main(){ 21 cin>>t; 22 for(int cs=1;cs<=t;++cs){ 23 cin>>r>>b; 24 if(r<=1){ 25 printf("Case #%d: 0/1\n",cs); 26 continue; 27 }else{ 28 int
fz=r*(r-1),fm=(r+b)*(r+b-1),re=__gcd(fz,fm); 29 printf("Case #%d: %d/%d\n",cs,fz/re,fm/re); 30 } 31 } 32 return 0; 33 }
View Code

比賽的時候順利簽到1A,雖然我敲得還是慢了一點


CCameraman

題意:一個$W\times H$的矩形房間,給定Bob位置$(x,y)$,以及$m$個障礙物的座標$(x_i,y_i)$,需要Alex站在一個點拍照,拍照的範圍是Alex站的位置為頂點的一個角(可以大於180°),要求拍照範圍內必須拍到Bob而不能拍到其他障礙物,求能拍到的牆壁的最大長度

思路:這題是個假題,當時我們隊的思路是Alex站在Bob的位置拍攝,以Bob為頂點,對其他障礙物進行一個極角排序,然後拍攝角度兩條邊卡在兩個相鄰障礙物上,求投影長度來更新答案。比賽的時候最後1個多小時就在rush這道題,由於對板子還不是很熟,加上處理操作繁瑣,沒能寫完。但是賽後NB群友提出了在Bob在障礙物凸包外的話,這樣的做法就假了,甚至連樣例都過不去。

程式碼:待補(雖然是假演算法,但是還是熟練一下極角排序)


E Exam Results 二分+樹狀陣列

題意:$n$個學生,及格率$p$,每個學生有可能獲得兩個分數:最高分$a_{i}$和最低分$b_{i}$,及格線為$n$個學生中的最高分*$p%$,問最好的情況下有多少的人能及格

思路:對所有分數進行一個展開排序,列舉最高分,得到及格線,然後二分查詢到及格線的位置,查詢及格線到當前列舉的最高分的區間,查詢區間內有多少個學生。可以使用樹狀陣列維護區間裡的種類數。維護手法:在第一次出現某個種類時,在相應的樹狀陣列的位置進行+1,之後出現某個種類時,在樹狀陣列的$last[i]$處$-1$,然後查詢就通過樹狀陣列進行區間相減得到種類。需要注意的是有可能從最低的分數開始列舉的最高分可能無效,因為可能當前的最高分不能包括所有$n$個人,所以可以從所有人的最低分的最高分開始列舉,也可以字首和維護當前分數能包括多少人(P.S:樹狀陣列能維護是因為這題的查詢算是已經離線過的,對於其他無序的提問,就得對詢問進行離線處理,例題:luogu P1972 [SDOI2009]HH的項鍊

程式碼:

#include <bits/stdc++.h>

#define debug(...)  fprintf(stderr,__VA_ARGS__)
#define DEBUG printf("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define Debug debug("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define _max(a,b) (a)>(b)? (a):(b)
#define _min(a,b) (a)<(b)? (a):(b)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
typedef pair<int,ll> pil;
typedef pair<ll,ll> pll;

int t;
int n,P;
const int MAXN=2e5+10;
struct p{
    int val,id;
    bool operator <(const p& rh)const{
        return val==rh.val? id<rh.id:val<rh.val;
    }
}pt[MAXN<<1];
int lst[MAXN<<1],pr[MAXN<<1],ls[MAXN<<1],vs[MAXN];
ll tr[MAXN<<1];

void init(){
    rep(i,0,n)   lst[i]=0;
    rep(i,0,n)  vs[i]=0;
    rep(i,0,n<<1)   tr[i]=0;
}

int lowbit(int x){return x&-x;}

void upd(int x,int k){
    for(;x<=2*n;x+=lowbit(x)) tr[x]+=k*1ll;
}

ll qr(int x){
    ll ret=0;
    for(;x;x-=lowbit(x)){
        ret+=tr[x];
    }
    return ret;
}

int main(){
    cin>>t;
    for(int cs=1;cs<=t;++cs){
        cin>>n>>P;
        init();
        double rat=P*0.01;
        int x,y;
        rep(i,1,n){
            scanf("%d%d",&x,&y);
            pt[i].id=pt[i+n].id=i;
            pt[i].val=x;
            pt[i+n].val=y;
        }
        sort(pt+1,pt+1+2*n);
        rep(i,1,n<<1) ls[i]=pt[i].val;
        rep(i,1,n<<1){
            if(!vs[pt[i].id]){
                vs[pt[i].id]=1;
                pr[i]=pr[i-1]+1;
            }else{
                pr[i]=pr[i-1];
            }
        }
        ll ans=0;
        rep(i,1,n<<1){
            if(lst[pt[i].id]){
                upd(lst[pt[i].id],-1);
            }
            upd(i,1);
            lst[pt[i].id]=i;
            if(pr[i]<n) continue;
            int res=ceil(pt[i].val*rat);
            int pos=lower_bound(ls+1,ls+2*n+1,res)-ls;
            ans=max(ans,qr(i)-qr(pos-1));
        }
        printf("Case #%d: %lld\n",cs,ans);
    }
    return 0;
}
View Code

比賽的時候隊友Y想到了展開排序,然後我想到了列舉+二分,在思考維護區間種類的時候,儘管做過樹狀陣列維護區間的題目,但是還是搞出了一個錯的維護方法,幸虧隊友H資料結構功力深厚,及時寫出了正解,成功1A


F Friendly Group 貪心

題意:$n$個人,$m$組朋友關係,選擇一些人${i}$,定義快樂值$p$為 $選擇的人的集合之間的朋友關係數量-選擇的人數$

思路:可以發現,如果選擇了一個人,那麼選擇所有他的朋友並不會使得快樂值更劣,因為$p_{next}=p_{current}-select_{next}+e_{next} \geq p_{current}$,所以我們對於一個聯通子圖,我們要麼全選,要麼全不選。DFS更新答案即可。

程式碼:

#include <bits/stdc++.h>

#define debug(...)  fprintf(stderr,__VA_ARGS__)
#define DEBUG printf("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define Debug debug("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define _max(a,b) (a)>(b)? (a):(b)
#define _min(a,b) (a)<(b)? (a):(b)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
typedef pair<int,ll> pil;
typedef pair<ll,ll> pll;

const int MAXN=3e5+10;

int t,n,m;
vector<int> g[MAXN];
int vis[MAXN],in[MAXN];
ll ans=0;

void init(){
    ans=0;
    for(int i=0;i<=n;++i)   g[i].clear();
    rep(i,0,n)  vis[i]=0,in[i]=0;
}

void dfs(int u,ll& cnt,int& tot){
    vis[u]=1;
    cnt+=in[u];
    tot++;
    for(int x:g[u]){
        if(!vis[x]){
            dfs(x,cnt,tot);
        }
    }
}

int main(){
    cin>>t;
    for(int cs=1;cs<=t;++cs){
        cin>>n>>m;
        init();
        int u,v;
        
        rep(i,1,m){
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
            in[u]++;
            in[v]++;
        }
        ll cnt=0;
        int tot=0;
        rep(i,1,n){
            if(!vis[i]){
                cnt=0;
                tot=0;
                dfs(i,cnt,tot);
                cnt=max(0ll,cnt/2-tot);
                ans+=cnt;
            }
        }
        printf("Case #%d: %lld\n",cs,ans);
    }
    return 0;
}
View Code

這題隊友Y當時很快想出了正解,但是沒有考慮圖不連通的情況,然後隊友H就提議用SCC來寫,然後因為手滑失誤+調板子,成功+3


G Good Number 簡單數學

題意:給定$n$和$k$,求不超過$n$的$i$使得$i$能整除$\lfloor\sqrt[k]{i}\rfloor$,求$i$的個數

思路:列舉$i$,統計$\left[i^{k},(i+1)^{k}\right)$能被$i$整除的個數,求和

程式碼:

#include <bits/stdc++.h>

#define debug(...)  fprintf(stderr,__VA_ARGS__)
#define DEBUG printf("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define Debug debug("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define _max(a,b) (a)>(b)? (a):(b)
#define _min(a,b) (a)<(b)? (a):(b)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
typedef pair<int,ll> pil;
typedef pair<ll,ll> pll;

int t;
ll n,k;
const ll MAXN = 1e9+10;
ll qpow(ll a,ll b){
    ll ret=1;
    if(a==1)    return 1;
    for(ll i=1;i<=b;++i){
        ret=ret*a;
        if(ret>n)    return 0;
    }
    return ret;
}

int main(){
    cin>>t;
    for(int cs=1;cs<=t;++cs){
        cin>>n>>k;
        if(k==1){
            printf("Case #%d: %lld\n",cs,n);
            continue;
        }
        ll l=1,r=1;
        ll ans=0;
        bool fl=0;
        for(ll i=1;i<n;++i){
            l=qpow(i,k),r=qpow(i+1,k);
            if(r==0){
                fl=1;
                r=n+1;
            }
            ans+=(r-l+i-1)/i;
            //cout<<i<<" "<<ans<<endl;
            if(fl)  break;
        }
        printf("Case #%d: %lld\n",cs,ans);
    }
    return 0;
}
View Code

比賽的時候我還在讀F的時候,隊友H和Y就已經想出正解了,隊友H直接上機拍程式碼1A


KKingdom's Power 樹形dp

題意:給定一棵樹,根節點有無數個人,每一個單位時間,可以使一個人移動到一個相鄰節點,求走完整棵樹的最小時間花費

思路:首先,對於一個人,不考慮根節點派出一個人,肯定是從深度小的子樹轉移到深度大的子樹才可能更優,先求每一顆子樹的深度,然後對每顆子樹的根節點的子結點的子樹深度排序。其次,一顆子樹被走完所派出的人要麼是從兄弟子樹來的,要麼是從整棵樹的根節點來的,在更新了一個子節點之後,需要更新可能傳給下一個節點的值,但是這個值只能用一次,因此需要注意一下細節。(可能解釋的不是很清楚,可以看一下參考部落格:https://www.cnblogs.com/crazyfz/p/13838422.html)

程式碼:

#include <bits/stdc++.h>

#define debug(...)  fprintf(stderr,__VA_ARGS__)
#define DEBUG printf("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define Debug debug("Passing [%s] in LINE %d\n",__FUNCTION__,__LINE__)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define _max(a,b) (a)>(b)? (a):(b)
#define _min(a,b) (a)<(b)? (a):(b)

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
typedef pair<int,ll> pil;
typedef pair<ll,ll> pll;

const int MAXN=1e6+10;

int t,n;
ll ans=0;

int val[MAXN];
int fa[MAXN];
vector<pii> g[MAXN];

void init(){
    for(int i=0;i<=n;++i)   g[i].clear();
    ans=0;
}

int getdep(int u){
    if(g[u].empty())    return 1;
    for(int i=0;i<g[u].size();++i){
        g[u][i].first=getdep(g[u][i].second);
    }
    sort(g[u].begin(),g[u].end());
    return g[u].back().first+1;
}

int dfs(int u,int d,int v){
    val[u]=v;
    if(g[u].empty())    return 1;
    int res=v;
    for(int i=0;i<g[u].size();++i){
        res=min(d,dfs(g[u][i].second,d+1,res+1));
    }
    return res+1;
}

int main(){
    cin>>t;
    for(int cs=1;cs<=t;++cs){
        scanf("%d",&n);
        init();
        int x;
        rep(i,2,n){
            scanf("%d",fa+i);
            g[fa[i]].push_back(pii(0,i));
        }
        getdep(1);
        dfs(1,0,0);
        rep(i,1,n)  if(g[i].empty()) printf("%d->%d\n",i,val[i]);
        rep(i,1,n)  if(g[i].empty()) ans+=val[i];
        printf("Case #%d: %lld\n",cs,ans);
    }
    return 0;
}
View Code

比賽的時候過了銅牌題之後我們隊開了C和K,然後我們重點rush C,rush失敗,K當時我也和隊友Y討論了蠻久,一直看不出來考的什麼,我們當時也只討論出來需要預處理子樹深度,然後想著有什麼貪心的方法解決,最後思路確實不大清晰,只能把重心放在C上。


比賽總結,A簽到,隊友說思路我寫,可能因為緊張,碼的磕磕絆絆,還好1A了;G,隊友H和Y的輸出,1A;F,隊友H的做法有點小題大做了,中間一些小失誤,+3;E,跟隊友Y討論了思路,我碼,資料結構維護部分扔給了隊友H,寫完之後還測了好幾組資料,確認沒問題就交了,1A;最後我們看榜,有C和K是比較可做的,然後我讀C,隊友H和Y讀K,我讀完題跟Y和H交流了題意,認為C就是極角排序,就讓隊友H上機寫了,我跟隊友Y討論K,無果,我們就都去幫隊友H debug( 結對程式設計(bushi )。比賽結束,摸到了很極限的銅尾。

跟兩個隊友第一次組隊就能摸一塊銅,看來以後得抓緊訓練了(