寒假训练赛02 补题

每日吐槽
昨晚进行了寒假训练赛02,白天还打了牛客的训练营,被暴打!!!晚上又来csoj受虐。这场发挥较上次好。rank18,A了三题。(不过第三题调试了好久,因为样例实在太少了。)终于会写二分(以前每次都想不到二分)!

题解
A.红包接龙
题意:有n-1轮,每次编号为 a i a_i ai的人给编号为 a i + 1 a_{i+1} ai+1的人发i的红包,让你去计算收入最多的人收获的了多少?
思路:直接模拟就可以,但注意到下标比较大,直接拿数组存不下,就需要离散化或者哈希来处理。
代码

void solve(){
    int n;
    cin>>n;
    map<int,int> mp;
    vector<int> a(n);
    for(auto &x:a){
      cin>>x;
    }
    for(int i=1;i<n;i++){
      mp[a[i-1]]-=i;
      mp[a[i]]+=i;
    }
    int ans=0;
    for(auto &[k, v]:mp){
      ans=max(ans,v);
    }
    cout<<ans<<endl;
}

B.最后一班
题意:初始值为0,你有三种操作,对于第i次操作:
1.你可以在原有值之上加上i。
2.你可以在原有值之上减去i。
3.你可以选择不变
问你至少多少次操作可以使值变为n。
思路:通过分析,我们只需要找到最小的t满足 1 + 2 + 3 + . . . t ≥ n 1+2+3+...t\geq n 1+2+3+...tn即可,因为我们一定可以通过不选一些数(第三种操作),去凑出n。所以直接用高斯求和解决即可。这里我二分了一下。

void solve(){
    LL n;
    cin>>n;
    LL l=1,r=1e9;
    while(l<r){
        LL mid=l+r>>1;
        if((mid*(mid+1))>=2*n) r=mid;
        else l=mid+1;
    }
    cout<<l<<endl;
}

C.勇者兔
题意:给你n个区间,每次区间上有一个宽度是区间宽度的怪兽,你可以发动一次技能,宽度为w,消灭被技能碰到的怪兽,问你消灭所有怪兽最少需要发动多少次技能?
思路:贪心,我们对所有区间按照右端点排序,每次都在[r,r+w]释放技能。每次记录上一次释放技能的区间,如果这一区间的左端点小于等于r或者这个区间是释放技能的子区间。那么都是可以被上一次释放得到技能直接一起消灭的,不用计算。

代码

vector<PLL> a;
bool cmp(PLL a,PLL b){
    if(a.ss!=b.ss) return a.ss<b.ss;
    return a.ff<b.ff;
}
void solve(){
    LL n,w;
    cin>>n>>w;
    for(int i=1;i<=n;i++){
       int b,l,d,r;
       cin>>b>>l>>d>>r;
       a.pb({l,r});
    }
    sort(all(a),cmp);
    LL cnt=0;
    LL l=0,r=0;
    for(auto x:a){
     if(x.ff<=l||x.ff>=l&&x.ff<=r) continue;
     cnt++;
     l=x.ss;
     r=x.ss+w-1;
    }
     cout<<cnt<<endl;
}

D.兔兔爱消除
题意:给我们一个矩阵,矩阵里面有一些数字,现在我们可以将相同的数字进行消除,每次消除可以得到 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| x1x2+y1y2的分数,求最大能够得到的分数。
思路:我们发现对每种类型的物品,会从 个物品开始,每次消除一个,直到剩下 1 个为止,如果把这个过程反过来:从 1 个物品开始,每次添加一个物品,直到物品个数达到 。发现这就是一个最大生成树(将Kruskal的排序改一下即可)的过程。所以初始化一下整张图,跑一遍最大生成树即可。

代码:

/Kruskal算法求最小生成树 模板
int p[N],a[55][55];
int n;
struct EAGE{
    int a,b,w;
};
vector<EAGE> eages;
bool cmp(EAGE A,EAGE B){//注意这里排序需要反过来
    return A.w>B.w;
}
int find(int x){//并查集查找
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int kruskal(){
    sort(eages.begin(),eages.end(),cmp);
    for(int i=0;i<=n*n;i++) p[i]=i;
     int res=0,cnt=0;
    for(int i=0;i<eages.size();i++){
        int a=eages[i].a,b=eages[i].b,w=eages[i].w;
        a=find(a),b=find(b);
        if(a!=b){
            p[a]=b;
            res+=w;
            cnt++;
        }
    }
    if(cnt==n*n) return INF;
    else return res;
}
void solve(){
    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cin>>a[i][j];
        }
    }
    auto id=[&](int i,int j){//匿名函数,给每个位置一个独特的下标
       return i*n+j;
    };
    for(int i=0;i<n;i++){//将相同的点之间进行建边,权值就是分数
        for(int j=0;j<n;j++){
            for(int ii=0;ii<n;ii++){
                for(int jj=0;jj<n;jj++){
                    if(a[i][j]==a[ii][jj]&&(i!=ii||j!=jj)){
                        eages.push_back({id(i,j),id(ii,jj),abs(i-ii)+abs(j-jj)});
                    }
                }
            }
        }
    }
    int ans=kruskal();
    cout<<ans<<endl;
}

E.吃席兔
题意:给你一棵树,并且标记一些节点,对于每一个节点,可以选择一个已经被标记的节点,并且向他的方向走一步。每次选择的节点不能相同,最后询问对于每个节点是否能够被标记或者走到被标记的结点。
思路:用 c n t i cnt_i cnti表示结点i的子树中有多少个结点被标记了。我们先考虑一个结点只向自己的子树内部移动,那么如果该节点能够走到被标记结点只有三种情况:1.该节点一开始就被标记了。2.有直接与该节点相连的结点被标记了。3.存在i的某个子节点j满足 c n t j ≥ 2 cnt_j \geq 2 cntj2并且j能够走到被标记的点中。(那么i一定可以到达j,然后按照j的走法接着走剩下的部分,就一定可以到达)。对于完整的问题。我们可以再次dfs一次,把父节点当做子节点进行转移。

代码:

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx; //邻接表存图,无向图,开两倍空间。
int n, a[N], ok[N], cnt[N];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int fa) {
    //第一种情况:当前点已经被标记
    if (a[u]) ok[u] = 1, cnt[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        dfs(j, u);
        //第二种情况:有直接与u结点相连的被标记的结点
        if (a[j]) ok[u] = 1;
        //第三种情况:有一个结点j满足cnt_j>=2&&ok[j]=1。
        if (cnt[j] > 1 && ok[j]) ok[u] = 1;
        cnt[u] += cnt[j];
    }
}
void dfs2(int u, int fa) {
    //cnt[1]-cnt[u]就是倒着看的情况下的cnt[fa]
    if (u != 1 && cnt[1] - cnt[u] > 1 && ok[fa]) ok[u] = 1;
    if (a[fa]) ok[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        dfs2(j,u);
     }
}
int main() {
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i < n; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    dfs(1, 1);
    dfs2(1, 1);

    for (int i = 1; i <= n; i++) cout << ok[i] << " \n"[i == n];
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Showball.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值