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;
}