2021牛客暑期多校训练营6 部分题解

C.Delete Edges

  • 题意
    给定一个包含 n n n个顶点的无向完全图,你可以选择三个顶点 x , y , z x,y,z x,y,z,如果 ( x , y ) , ( y , z ) , ( x , z ) (x,y),(y,z),(x,z) (x,y),(y,z),(x,z)这三条边没有被删除,你可以删除这三条边。你需要找到一种删除方案使得最后剩余边数 ≤ n \leq n n

  • 解题思路
    可以确定的是,如果我们选择 ( x , y , z ) (x,y,z) (x,y,z)删除,那么之后删除就不能是 ( x , y , z ) (x,y,z) (x,y,z)中的任意两个,因为它们之间没有边直接相连。
    所以,我们贪心的去想,怎么去避免没有重复出现的两两组合数,即可以保证 x < y < z x<y<z x<y<z,同时维护 x + y + z = 0 ( m o d    n ) x+y+z =0(\mod n) x+y+z=0(modn),那么我们就可以枚举 x , y x,y x,y来确定 z z z并记录操作即可。

  • AC代码

/**
  *@filename:C
  *@author: pursuit
  *@created: 2021-08-07 16:19
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl

using namespace std;

typedef pair<int,int> pii;
typedef long long ll;
const int N = 4e6 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;

int n,m;
struct node{
    int x,y,z;
};
node ans[N];
void solve(){
    int tot = 0;
    for(int i = 1; i <= n; ++ i){
        for(int j = i + 1; j <= n; ++ j){
            int k = n - i - j;
            if(k <= 0)k += n;
            if(k > j){
                ans[++ tot] = {i,j,k};
            }
        }
    }
    printf("%d\n", tot);
    for(int i = 1; i <= tot; ++ i){
        printf("%d %d %d\n", ans[i].x, ans[i].y, ans[i].z);
    }
}
int main(){	
    scanf("%d", &n);
    solve();
    return 0;
}

F.Hamburger Steak

  • 题意
    n n n个汉堡排和 m m m个锅,给出每个汉堡排需要煎的时间 t i t_i ti。一个汉堡排可以在一口锅中煎好,也可以分成两次在两个锅中煎好。一个锅同时只能煎一个汉堡排,一个汉堡排同时只能放到一个锅中。求一个方案使煎好所有的汉堡排所需要的时间最少(尽早煎完)。

  • 解题思路
    首先,根据题意,我们可以确定煎汉堡排的最小时间 T = m a x ( ⌈ ∑ i = 1 n t i ) m ⌉ , m a x ( t i ) ) T=max(\lceil\frac{\sum_{i=1}^nt_i)}{m}\rceil, max(t_i)) T=max(mi=1nti),max(ti))。所以我们可以按平均时间将汉堡排贪心的分配给锅。具体操作为利用优先队列存储每个汉堡排的编号和结束时间,然后遍历 m m m个锅,充分利用完平均时间即可。

  • AC代码

/**
  *@filename:F
  *@author: pursuit
  *@created: 2021-08-06 21:27
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl;

using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;

//m个平底锅,n个汉堡牛排,第i个汉堡需要t_i时间。其可以在一个锅中煎t_i分钟或者在两个不同的锅中煎a_i和b_i分钟,其中a_i+b_i=t_i。
//其中一个平底锅最多同时煎一个,一个汉堡最多同时放在一个锅中。
//最小化煎的时间。
//输出n行,其中每行第一个整数k,代表在几个锅中煎。其中k个元祖为平底锅编号id,开始时间和结束时间,即[l,r]。
int n,m;
struct node{
    int id;
    ll l,r;
};
vector<node> plans[N];
priority_queue<pii> q;//存储结束时间和汉堡排的编号。
ll T;
void solve(){
    T = max((T + m - 1) / m,1LL * q.top().first);//获取最小耗时T.
    ll time = 0;//当前时刻。
    int id = 1;//平底锅编号。
    while(!q.empty()){
        int t = q.top().first, i = q.top().second;
        q.pop();
        if(time + t <= T){
            //说明时间足够。
            plans[i].push_back({id,time,time + t});
            time += t;
        }
        else{
            //说明时间不够,超出了,我们需要用两个锅分配。
            plans[i].push_back({id,time,T});
            t -= (T - time);//获取还需要操作的时间。
            id ++;
            time = t;
            plans[i].push_back({id,0,time});
        }
        if(time == T){
            time = 0;
            id ++;
        }
    }
    for(int i = 1; i <= n; ++ i){
        if(plans[i].size() == 1){
            printf("1 %d %lld %lld\n", plans[i][0].id, plans[i][0].l,plans[i][0].r);
        }
        else{
            printf("2 %d %lld %lld %d %lld %lld\n", plans[i][1].id, plans[i][1].l,plans[i][1].r, plans[i][0].id, plans[i][0].l,plans[i][0].r);
        }
    }
}
int main(){
    scanf("%d%d", &n, &m);
    int x;
    for(int i = 1; i <= n; ++ i){
        scanf("%lld", &x);
        q.push({x,i});//煎所需的时间。
        T += x;
    }	
    solve();
    return 0;
}

H.Hopping Rabbit

  • 题意
    平面上有 n n n 个矩形,给定 d d d,需要找到一个位置 ( x , y ) (x,y) (x,y),使得所有 ( x + k d , y + k d ) (x+kd,y+kd) (x+kd,y+kd) 均不落在矩形中。

  • 解题思路
    由于我们能到达的位置是周期重复的,所以实际上我们可以将所有的矩形移动到 ( 0 , 0 ) (0,0) (0,0) ( d , d ) (d,d) (d,d)的范围内,然后在里面找一个没有被矩形覆盖的点即可,这样找到的这个点一定是不落在矩形上的。
    那么考虑矩形平移,矩形可能会被分成很多个部分,我们需要对其左上角和右上角坐标对 d d d取模然后得到应该在的位置,根据这个位置确定分成多少个矩形。
    处理完这些矩形之后,就可以利用扫描线维护这些矩形求答案了。

/**
  *@filename:H
  *@author: pursuit
  *@created: 2021-08-07 13:48
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl;
#define x first 
#define y second

using namespace std;

typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;

//用左下角(x_1,y_1)和右上角(x_2,y_2)来描述一个陷阱矩阵。
int n,d;//n个陷阱,d为跳跃的长度。
struct Line{
    int yl,yr;//左右端点。
    int d;//是入边还是出边。
    Line(){};
    Line(int yl,int yr,int d) : yl(yl),yr(yr),d(d){};
};
vector<Line> scan[N];//扫描线。scan[i]代表扫描线高度为i的集合。利用桶排序。
struct node{
    int l,r,minn,lazy;
}tree[N << 2];
void buildTree(int rt,int l,int r){
    //cout << rt << " " << l << " " << r << endl;
    tree[rt] = {l,r,0,0};
    if(l == r){
        return;//说明到了叶子结点。
    }
    int mid = l + r >> 1;
    buildTree(rt << 1,l,mid);
    buildTree(rt << 1 | 1,mid + 1,r);
}
void pushup(int rt){
    tree[rt].minn = min(tree[rt << 1].minn,tree[rt << 1 | 1].minn);
}
void pushdown(int rt){
    if(tree[rt].lazy){
        int &lz = tree[rt].lazy;
        tree[rt << 1].minn += lz;
        tree[rt << 1].lazy += lz;
        tree[rt << 1 | 1].minn += lz;
        tree[rt << 1 | 1].lazy += lz;
        lz = 0;
    }
}
void update(int rt,int l,int r,int d){
    if(l <= tree[rt].l && r >= tree[rt].r){
        //整个区间都被覆盖了。
        tree[rt].minn += d;
        tree[rt].lazy += d; 
        return;   
    }
    pushdown(rt);
    int mid = (tree[rt].l + tree[rt].r) >> 1;
    if(l <= mid){
        update(rt << 1,l,r,d);
    }
    if(r > mid){
        update(rt << 1 | 1,l,r,d);
    }
    pushup(rt);
}
int query(int rt,int l,int r){
    if(l <= tree[rt].l && r >= tree[rt].r){
        return tree[rt].minn;
    }
    pushdown(rt);
    int mid = tree[rt].l + tree[rt].r >> 1;
    int res = INF;
    if(l <= mid){
        res = min(res,query(rt << 1,l,r));
    }
    if(r > mid){
        res = min(res,query(rt << 1 | 1,l,r));
    }
    return res;
}
void mod(pii &a){
    a.x = (a.x % d + d) % d, a.y = (a.y % d + d) % d;
}
void add(int x1,int x2,int y1,int y2){
    //cout << x1 << " " << x2 << " " << y1 << " " << y2 << endl;
    scan[x1].push_back(Line(y1,y2,1));//入边。
    scan[x2 + 1].push_back(Line(y1,y2,-1));//出边。
}
void push(pii a,pii b){
    if(a.x <= b.x){
        if(a.y <= b.y){
            //说明只能分成一个矩形。
            add(a.x,b.x,a.y,b.y);
        }
        else{
            add(a.x,b.x,a.y,d - 1),add(a.x,b.x,0,b.y);
        }
    }
    else{
        //a.x > b.x;
        if(a.y <= b.y){
            add(0,b.x,a.y,b.y),add(a.x,d - 1,a.y,b.y);
        }
        else{
            //a.y > b.y;
            add(0,b.x,a.y,d - 1),add(0,b.x,0,b.y);
            add(a.x,d - 1,a.y,d - 1),add(a.x,d - 1,0,b.y);
        }
    }
}
void solve(){
    pii ans = pii(-1,-1);
    buildTree(1,0,d - 1);
    for(int i = 0; i < d; ++ i){
        if(ans.x != - 1)break;
        for(auto &l : scan[i]){
            update(1,l.yl,l.yr,l.d);
        }
        if(query(1,0,d - 1) == 0){
            //判断是否存在没有被覆盖的位置。
            for(int j = 0; j < d; ++ j){
                if(query(1,j,j) == 0){
                    ans.x = i,ans.y = j;
                    break;
                }
            }
        }
    }
    if(ans.x != - 1){
        puts("YES");
        printf("%d %d\n", ans.x, ans.y);
    }
    else{
        puts("NO");
    }
}
int main(){
    scanf("%d%d", &n, &d);
    pii a,b;
    for(int i = 1; i <= n; ++ i){
        scanf("%d%d%d%d", &a.x, &a.y, &b.x, &b.y);
        b.x --,b.y --;
        if(b.x - a.x + 1 >= d){
            b.x = d - 1,a.x = 0;
        }
        if(b.y - a.y + 1 >= d){
            b.y = d - 1,a.y = 0;
        }
        mod(a),mod(b);
        push(a,b);
    }
    solve();
    return 0;
}

I.Intervals on the Ring

  • 题意
    给出环上的一组区间,你需要构造环上的一组区间使得这些区间的交是给定的区间的并。

  • 解题思路
    由于给出的间隔都是不相交的。所以我们能保证相邻两个区间的左右端点合在一起能覆盖整个区间,所以 [ l 1 , r 2 ] , [ l 2 , r 1 ] [l_1,r_2],[l_2,r_1] [l1,r2],[l2,r1]这个构建的区间交即是这两个区间的并。例如 n = 5 , [ 1 , 2 ] , [ 3 , 4 ] n=5,[1,2],[3,4] n=5,[1,2],[3,4]这个区间,我们则会选取 [ 1 , 4 ] , [ 3 , 2 ] [1,4],[3,2] [1,4],[3,2]

  • AC代码

/**
  *@filename:I
  *@author: pursuit
  *@created: 2021-08-06 19:37
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl;
#define l first 
#define r second
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 1e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;

//若l <= r,则区间表示为[l,r]
//若l > r,则区间表示为[l,l + 1...n,1...r];
int t,n,m;
int l,r;
void solve(){
    vector<pii> interval(m);
    for(int i = 0; i < m; ++ i){
        scanf("%d%d", &l, &r);
        interval[i] = {l,r};
    }
    printf("%d\n", m);
    sort(interval.begin(), interval.end());
    for(int i = 0; i < m; ++ i){
        printf("%d %d\n", interval[i].l, interval[(i + m - 1) % m].r);
        //cout << (i + m - 1) % m << endl;
    }
}
int main(){
    scanf("%d", &t);
    while(t -- ){
        scanf("%d%d", &n, &m);
        solve();
    }	
    return 0;
}

J.Defend Your Country

  • 题意
    A国城市的地图可以看成一个简单的无向连通图,由n个城市和m条道路组成。B国想摧毁一些道路,A国可以决定提前关闭一些道路。
    每个城市都有一个重量级 a i a_i ai。如果连通块中城市数量为奇数,可以重要总水平和为总和的负数,否则为正数。你需要使得总重量级最大。

  • 解题思路
    根据题意,我们自然是想保证每个连通块的城市数量均为偶数,而一开始保证连通,所以如果 n n n是偶数,那么一定满足条件。我们考虑 n n n为奇数的时候,此时我们一定要删除点来使得剩余的连通块中城市数量为偶数,那么对于非割点,我们删除不会影响图的连通性,所以易得删除非割点只需要选取最小的即可,那么考虑割点,如果删除割点,那么一定要保证剩余的连通块城市数量为偶数,因为如果不保证就会导致还要继续删除以删除更多的点。
    综上实际上我们都是只要删除一个点即可,我们用tarjan算法找割点,同时用siz数组记录子树的节点总数。

  • AC代码

/**
  *@filename:J
  *@author: pursuit
  *@created: 2021-09-26 19:19
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl

using namespace std;

typedef pair<int,int> pii;
typedef long long ll;
const int N = 1e6 + 10;
const int P = 1e9 + 7;
const int INF = 2e9;


//删去非割点显然只需要删除一个,所以考虑删掉割点的情况取最优即可。
//我们删除割点的时候一定不会删除掉非割点。
int t, n, m, a[N];
struct node{
    int to, next;
}edges[N << 1];
int head[N], tot;
void add(int u, int v){
    edges[++ tot].next = head[u], edges[tot].to = v, head[u] = tot;
}
int idx, low[N], dfn[N];
int siz[N];//siz[i]表示以i为结点的子树结点数总和。
bool cut[N], tag[N];//cut[i]表示i结点是否为割点, tag[i]表示i这个割点割去之后剩下的连通块城市数量是否为偶数。
ll res;
void init(){
    tot = idx = 0;
    res = 0;
    for(int i = 1; i <= n; ++ i)head[i] = low[i] = dfn[i] = siz[i] = 0, cut[i] = tag[i] = false;
}
void tarjan(int u, int fu){
    dfn[u] = low[u] = ++ idx;
    int v, son = 0;
    siz[u] = 1;
    for(int i = head[u]; i; i = edges[i].next){
        v = edges[i].to;
        if(v == fu)continue;
        if(!dfn[v]){
            ++ son;
            tarjan(v, u);
            siz[u] += siz[v];
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]){
                if(fu)cut[u] = true;//为割点。
                if(siz[v] & 1){
                    //说明子树结点为奇数。
                    tag[u] = true;
                }
            }
        }
        else{
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(!fu && son > 1)cut[u] = true;
}
void solve(){
    if(n % 2 == 0){
        printf("%lld\n", res);
        init();
        return;
    }
    tarjan(1, 0);
    int minn = INF;
    for(int i = 1; i <= n; ++ i){
        if(!cut[i] || !tag[i]){
            //cout << a[i] << " ";
            minn = min(minn, a[i]);
        }
    }
    //cout << endl;
    printf("%lld\n", res - 2 * minn);
    init();
}
int main(){	
    scanf("%d", &t);
    while(t -- ){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++ i){
            scanf("%d", &a[i]);
            res += a[i];
        }
        for(int i = 1, u, v; i <= m; ++ i){
            scanf("%d%d", &u, &v);
            add(u, v), add(v, u);
        }
        solve();
    }
    return 0;
}
posted @   unique_pursuit  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示