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;
}
posted @ 2023-09-15 18:06  touchfishman  阅读(42)  评论(0)    收藏  举报