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__) 6View Code#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 intfz=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 }
比賽的時候順利簽到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 )。比賽結束,摸到了很極限的銅尾。
跟兩個隊友第一次組隊就能摸一塊銅,看來以後得抓緊訓練了(