2020CCPC秦皇岛 AEFGIK
A. A Greeting from Qinhuangdao (签到,小学概率题)
题意
给r个红气球,b个蓝气球,随机取两个球(不放回),都是红色气球的概率
思路
小学概率问题
- 红气球数小于2,则概率为0,题目这要输出
1/0 - 大于等于二,概率
P = r/(r+b) * (r-1)/(r+b)
代码
int main() {
fst;
int t; cin>>t;
for(int i=1;i<=t;i++){
int r,b; cin>>r>>b;
if(r<2) cout<<"Case #"<<i<<": "<<0<<"/"<<1<<le;
else{
int p = r*(r-1);
int q = (r+b)*(r+b-1);
int gcd = __gcd(p,q);
cout<<"Case #"<<i<<": "<<p/gcd<<"/"<<q/gcd<<le;
}
}
return 0;
}
E. Exam Results (排序,双指针)
题意
给n个学生,学生i发挥好时得a[i]分,发挥差的时候得b[i]分,设学生最高分为max,则及格线为 max*p
求最多有多少学生能及格
思路
这题其实排个序,双指针移移就行。
刚看到这题时,我的想法是把一个学生成绩看成一个区间[a,b]
而处理区间相交问题时我们经常用的方法是把区间两头拆开,变成[a,id],[b,id],丢进去排序(按照分数降序)
排完序后我们就可以枚举最高分,得到及格分。
那么我们的及格人数显然是在最高分到最低分之间出现的学生
我们可以使用一个vis数组防止重复计数
还有一个问题是,如何判断当前的状态是合法的,会不会有的学生的最低分会比我当前枚举到的最高分还高
我们可以在一开始求学生的发挥差的时候最高分 maxb = max(b[1],b[2],..)
当最高分小于maxb时,就会存在有人的最低分maxb使状态不合法
而当最大分数大于等maxb时,其他人取最低分就一定能使状态合法
代码
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const int mod=998244353;
struct node{
int x,id;
bool operator < (const node &nxt) const{
return x > nxt.x;
}
};
void solve(int t){
int n; cin>>n;
double p; cin>>p;
vector<node> a(2*n);
int maxb = 0;
for(int i=0;i<2*n;i+=2){
cin>>a[i].x>>a[i+1].x;
maxb = max(a[i+1].x,maxb);
a[i].id = a[i+1].id = i/2;
}
sort(a.begin(),a.end());
vector<int> vis(n);
int cnt = 0,l=0,ans = 0;
for(auto [x,id] : a){
if(x<maxb) continue;
int ok = (x*p+99)/100; //及格线
while(l<2*n && a[l].x>=ok){
if(vis[a[l].id]==0) cnt++;
vis[a[l].id]++;
l++;
}
ans = max(ans,cnt);
vis[id]--;
if(vis[id]==0) cnt--;
}
cout<<"Case #"<<t<<": "<<ans<<le;
}
int main() {
fst;
int t; cin>>t;
for(int i=1;i<=t;i++){
solve(i);
}
return 0;
}
F.Friendly Group (并查集)
题意
n个人,其中有m对朋友关系,选择k个人参加,每个人的贡献为-1,而每有一对朋友关系则贡献+1
思路
可以把每个人抽象为点,朋友抽象为边
对于一个联通块来说,他的贡献就是边的个数-点的个数
所以我们可以用并查集维护每个连通块的边数和点的个数
注意最后我们得到的是多个连通块,取所有贡献为正的联通块即可
代码
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+50;
const int mod=998244353;
int fa[N],sz[N],d[N];
int find(int x){
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void solve(int t){
int n,m; cin>>n>>m;
for(int i=1;i<=n;i++) fa[i] = i,sz[i] = 1,d[i] = 0;
int ans = 0;
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
int x = find(u),y = find(v);
if(x==y) d[x]++;
else if(sz[x]>=sz[y]){
fa[y] = x;
sz[x] += sz[y];
d[x] += d[y];
d[x]++;
}
else{
fa[x] = y;
sz[y] += sz[x];
d[y] += d[x];
d[y]++;
}
}
for(int i=1;i<=n;i++){
if(fa[i]==i){
ans += max(0,d[i]-sz[i]);
}
}
cout<<"Case #"<<t<<": "<<ans<<le;
}
int main() {
fst;
int t; cin>>t;
for(int i=1;i<=t;i++){
solve(i);
}
return 0;
}
G. Good Number(数学)
题意
给你n和k,求在1到n之间,有多少数满足: \(\lfloor \sqrt{x^k} \rfloor| x\)
即 x 开根号k次向下取整的结果能整除x
$ 0 < n,k <= 1e9 $
思路
赛中队友一发过的签到题,但是今天补反倒卡了我好一会
首先k的大小超过30次后就没有意义了
这是因为 \(2^{30} >= 1e9\)
对于小于等于1e9的数,开根结果都是1,而1显然能整除任何数
所以我们可以对k和30取小,防炸
这样我们可以愉快地枚举\(\lfloor \sqrt{x^k} \rfloor\),简称j
得到区间 [(pow(j,k)-1),pow(j+1,k)-1]
再算这些区间里满足整除的数
代码
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const int mod=998244353;
ll pow(ll n,ll m){
ll res = 1;
while(m){
if(m&1) res *= n;
n *= n;
m >>= 1;
}
return res;
}
void solve(int t){
ll n,k; cin>>n>>k;
k = min(1ll*30,k);
if(k==1) cout<<"Case #"<<t<<": "<<n<<le;
else{
ll ans=0;
for(ll j=1;;j++){
ll r=pow(j+1,k);
r--;
if(r>=n){
ans+=n/j-(pow(j,k)-1)/j;
break;
}
else{
ans+=r/j-(pow(j,k)-1)/j;
}
}
cout<<"Case #"<<t<<": "<<ans<<le;
}
}
int main() {
fst;
int t; cin>>t;
for(int i=1;i<=t;i++){
solve(i);
}
return 0;
}
H. Holy Sequence
题意
在补了,在补了
思路
代码
I. Interstellar Hunter(exgcd)
题意
起始点(0,0),有两种操作
- 获得一种能力,向量(x,y),可以在这个方向(包括反方向)移动任意整数次
- 获得一个坐标(x,y),若根据目前的能力能到达,则可获得奖励w
问最终能获得多少奖励
思路
能到达的点集可使用两个基向量表示
假设我们目前有两个基向量(a,b),(d,0)
对于上面两个操作,无非就是合成和查询
合成操作
获得了一个新的向量 (a,b)
那么先用 (x,y) 和 (a,b) 合出一个(d',0)和一个(x',y')
简单组合一下 b*(x,y)-y*(a,b) = (b*x-y*a,0)
d' = b*x-y*a
不过这不是最优的,我们希望得到最小的d'。
因此两边同除以 gcd(b,y),根据贝祖定理可得 gcd(b,y)|b*x-y*a
y'也好求,因为(d',0)不会在这个方向有贡献 ,y' = gcd(y,b)
设系数 m,n
gcd(y,b) = m*y+n*b 通过exgcd求得 m,n 的一组解
m*(x,y) + n*(a,b) = (m*x+n*a,y')
x' = m*x+n*
再让(d',0) 和原本的 (d,0)合成,得到 (gcd(d,d'),0)
查询操作
得到一个坐标(a,b),现有基向量(x,y),(d,0)
也就是证明 k1(x,y)+k2(d,0) = (a,b) 有整数解
k1 = b/y , k2 =( a-k1*x)/d
记得注意特判
写着题的时候wa了半天,下面给出一些样例帮大家debug
样例1
1
4
1 4 0
1 6 0
2 2 2 2
2 8 0 2
输出1
Case #1: 4
样例2
1
7
1 298524 194112
1 262630 141495
1 257808 329439
1 404979 229482
1 374217 343326
1 434600 200685
2 384563 218544 250244706
输出2
Case #1: 250244706
代码
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const int mod=998244353;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x = 1;
y = 0;
return a;
}
ll q = exgcd(b,a%b,y,x);
y -= a/b*x;
return q;
}
void solve(int t){
int q; cin>>q;
ll x = 0,y = 0,d = 0,ans = 0;
ll op,a,b,w;
while(q--){
cin>>op;
if(op==1){
ll n,m;
cin>>a>>b;
if(b==0){
d = __gcd(d,a);
}
else{
ll g = exgcd(y,b,m,n);
d = __gcd(d,abs(b*x-y*a));
y = g;
x = m*x+n*a;
if(d) x = (x%d+d)%d; //对x取模,防炸
//可以取模是因为可以随便加(-d,0)
}
}
else{
cin>>a>>b>>w;
if(y&&b%y==0){
ll r = a - b/y*x; //横坐标之差
if((d&&r%d==0)||r==0){
ans += w;
}
}
else if(y==0){
if((d&&a%d==0)||a==0) ans += w;
}
}
cout<<x<<" "<<y<<" "<<d<<le;
}
cout<<"Case #"<<t<<": "<<ans<<le;
}
int main() {
fst;
int t; cin>>t;
for(int i=1;i<=t;i++){
solve(i);
}
return 0;
}
K.Kingdom's Power (树形dp,dfs,贪心)
题意
给你一颗树,根节点为1,根节点上有无数只军队,每秒钟可以选择一支军队,将他派遣到相邻节点上,现在的目标是,经过这颗树上的每个节点,注意是经过每个节点就行,问最少时间是多少
思路
随便画个图感受一下
对于上面这个图,最优策略是先派一只军队走到节点4后,先走最短的子链到5,假如从根节点再派军队到4,则要花费3秒,不如让到节点5的军队返回节点4,只要1秒,之后再走长度为2和长度为3的子链。
我们可以发现走最短的链是一个很明显的贪心,假如我们需要到达叶子节点的军队返回,这样所花费的时间最少。
所以我的思路是对子树的高度进行升序排序,这样做的好处很多,我们只需要关注上一个到达叶子节点的军队即可,因为它一定比其他叶子节点的军队到当前子树根节点的距离近。
另外这题还要对节点1特判,从节点1派军队到相邻节点不会花费额外时间,具体细节见如下代码
代码
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+50;
const int mod=998244353;
vector<int> g[N];
int d[N],h[N],ans,lst;
bool cmp(int x,int y){
return h[x] < h[y];
}
void predfs(int u,int dep){
d[u] = dep;
h[u] = 1;
for(auto v: g[u]){
predfs(v,dep+1);
h[u] = max(h[u],h[v]+1);
}
sort(g[u].begin(),g[u].end(),cmp);
}
void dfs(int u){
if(g[u].size()==0) lst = u;
for(auto i: g[u]){
if(u!=1){
if(lst) ans += min(d[u]-d[1],d[lst]-d[u]);
lst = 0;
}
else lst = 0;
dfs(i);
}
}
void solve(int id){
int n; cin>>n;
for(int i=1;i<=n;i++) g[i].clear();
for(int i=2;i<=n;i++){
int fa; cin>>fa;
g[fa].push_back(i);
}
ans = lst = 0;
predfs(1,0);
dfs(1);
cout<<"Case #"<<id<<": "<<ans+n-1<<le;
}
int main() {
fst;
int t; cin>>t;
for(int i=1;i<=t;i++){
solve(i);
}
return 0;
}

浙公网安备 33010602011771号