2021牛客寒假算法基础集训营5【解题报告】

本文涵盖了多个图论问题的解决方案,包括寻找最短路径、序列构造、树上博弈、石子游戏和树的染色问题。涉及动态规划、二分查找、记忆化搜索、优先队列和并查集等算法。通过对每个问题的详细分析和高效代码实现,展示了如何运用图论知识解决实际问题。

A 美丽的路径

题目描述

叶妹妹非常喜欢图论题,这天她出了一个图论题,有一个nn个点mm条边的无向图,其中第ii个点的点权为 a i a_i ai ,她定义一条点数为 k k k 路径: b 1 b_1 b1 b 2 b_2 b2 ,…, b k b_k bk ;其中点 b i − 1 b_{i-1} bi1与点 b i b_i bi 通过一条边直接相连 ( 2 ≤ i ≤ k ) (2\le i\le k) (2ik),所以路径中可以出现重复的点。她将一条路径的美丽值定义为:假设这条路径的点数为 k k k,那么这条路径的美丽值就是此路径上的所有点的点权中第 ⌊ k / 2 + 1 ⌋ \lfloor k/2+1 \rfloor k/2+1 小的点权。现在她会询问你是否存在起点为 s s s 号点并且终点为 t t t 号点的路径,如果存在则先输出 Y E S YES YES,再输出存在的所有路径中的最大的美丽值;否则直接输出 N O NO NO

输入描述:

有多组样例,第一行输入一个数TT,表述样例组数

对于每组样例,第一行先输入四个数 n , m , s , t n,m,s,t nmst,保证 1 ≤ s ≤ n , 1 ≤ t ≤ n 1\le s\le n,1\le t\le n 1sn1tn

第二行输入 n n n 个数,其中第 i i i 个数为 a i a_i ai ,表示第 i i i 个点的点权

接下来输入 m m m 行,第 i i i 行输入两个数 x i x_i xi y i y_i yi,表示点 x i x_i xi 与点 y i y_i yi之间有一条无向边,保证 1 ≤ x i ≤ n , 1 ≤ y i ≤ n 1\le x_i\le n,1\le y_i\le n 1xin1yin
【数据规模与约定】

1 ≤ T ≤ 10 , 1 ≤ n ≤ 2 ∗ 1 0 5 , 0 ≤ m ≤ 2 ∗ 1 0 5 , 1 ≤ a i ≤ 1 0 9 1\le T\le10,1\le n\le 2*10^5,0\le m\le2*10^5,1\le a_i\le10^9 1T101n21050m21051ai109

输出描述:

如果存在起点为 s s s 号点并且终点为 t t t 号点的路径,则第一行输出 Y E S YES YES,第二行输出存在的所有的路径中的最大的美丽值;否则直接输出 N O NO NO

分析:

首先,如果不存在从 s s s t t t 的路径,则直接输出 N O NO NO.

否则,二分最大美丽值,问题就变成了询问是否存在从 s s s t t t 的路径,且这条路径点权的中位数大于等于 m i d mid mid.

s s s t t t 的路径中,如果存在两个相邻的点且他们的点权都大于等于 m i d mid mid,那么我们可以在这两个点之间来回走,使得最终路径的中位数大于等于 m i d mid mid.

s s s t t t 的路径中,如果存在两个相邻的点且他们的点权都大于等于 m i d mid mid,那为了使得路径美丽值大于等于 m i d mid mid,最终路径一定是 “点权大于等于 m i d mid mid 的点” 与 “点权小于 m i d mid mid 的点” 交替走,且起点终点的点权不能同时小于 m i d mid mid.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;

const int M = (int)2e5;
const int N = (int)2e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)998244353;

int n, m, s, t;
int cnt, head[M + 5];
struct enode
{
   
   
    int v, nx;
} Edge[M * 2 + 5];

int a[M + 5];
bool vis[M + 5];
bool vis2[M + 5];

void init()
{
   
   
    cnt = 0;
    for(int i = 1; i <= n; ++i)
    {
   
   
        head[i] = -1;
        vis[i] = 0;
    }
}

void add(int u, int v)
{
   
   
    Edge[cnt].v = v;
    Edge[cnt].nx = head[u];
    head[u] = cnt++;
}

void dfs1(int u)
{
   
   
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
   
   
        int v = Edge[i].v;
        if(vis[v]) continue;
        vis[v] = 1;
        dfs1(v);
    }
}

void dfs2(int u, int mid)
{
   
   
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
   
   
        int v = Edge[i].v;
        if(vis2[v]) continue;
        if(a[u] >= mid && a[v] < mid || a[u] < mid && a[v] >= mid)
        {
   
   
            vis2[v] = 1;
            dfs2(v, mid);
        }
    }
}

bool check(int mid)
{
   
   
    for(int i = 1; i <= n; ++i)
    {
   
   
        if(vis[i] && a[i] >= mid)
        {
   
   
            for(int j = head[i]; ~j; j = Edge[j].nx)
            {
   
   
                if(a[Edge[j].v] >= mid) return 1;
            }
        }
    }

    if(a[s] < mid && a[t] < mid) return 0;
    for(int i = 1; i <= n; ++i) vis2[i] = 0;
    vis2[s] = 1; dfs2(s, mid);
    return vis2[t];
}

int read()
{
   
   
    int x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch))
    {
   
   
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch))
    {
   
   
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

void work()
{
   
   
    n = read(); m = read(); s = read(); t = read(); init();
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = 1, u, v; i <= m; ++i)
    {
   
   
        u = read(); v = read();
        add(u, v), add(v, u);
    }
    vis[s] = 1; dfs1(s);
    if(!vis[t]) {
   
   printf("NO\n"); return;}
    int l = 1, r = (int)1e9, mid;
    while(l < r)
    {
   
   
        mid = (l + r + 1) >> 1;
        if(check(mid))  l = mid;
        else            r = mid - 1;
    }
    printf("YES\n%d\n", r);
}

int main()
{
   
   
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    int T; scanf("%d", &T);
    while(T--) work();
//    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

B 比武招亲(上)

题目描述

众所周知,天姐姐只喜欢天下最聪明的人,为了找到这样的人,她决定比武招亲!

只见天姐姐在榜上留下了这样一道问题,谁做出来了就可以俘获她的芳心!

爱慕天姐姐已久的泽鸽鸽问询赶来,只见榜上写着:

给定 n,mn,mn,m,定义一种序列,构造方法如下:

1. 1. 1. [ 1 , n ] [1,n] [1,n] 中任意选择 m m m 次,得到了 m m m 个整数(显然数字可能相同);

2. 2. 2. 将选出的 m m m 个数字排序之后得到一个序列 a 1 , a 2 , . . . , a m {a_1,a_2,...,a_m} a1,a2,...,am

定义一个序列的贡献为 m a x { a 1 , a 2 , . . . , a m } − m i n { a 1 , a 2 , . . . , a m } max\{a_1,a_2,...,a_m\}−min\{a_1,a_2,...,a_m\} max{ a1,a2,...,am}min{ a1,a

### 关于2020年寒假算法基础集训营中的欧几里得算法 在2020年的寒假算法基础集训营中,确实存在涉及欧几里得算法的相关题目。具体来说,在第四场竞赛的第一题即为“A. 欧几里得”,该题目的核心在于利用扩展欧几里得定理来解决问题[^5]。 #### 扩展欧几里得算法简介 扩展欧几里得算法主要用于求解形如 ax + by = gcd(a, b) 的线性不定方程的一组特解(x,y),其中gcd表示最大公约数。此方法不仅能够计算两个整数的最大公因数,还能找到满足上述条件的具体系数xy。 对于给定的数据范围较小的情况可以直接通过递归来实现;而对于较大数据则需考虑效率优化问题。下面给出了一段基于C++语言编写的用于解决此类问题的模板代码: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; // 定义全局变量存储结果 int x, y; void ex_gcd(int a, int b){ if(b == 0){ x = 1; y = 0; return ; } ex_gcd(b, a % b); int tmp = x; x = y; y = tmp - (a / b) * y; } ``` 这段程序实现了经典的扩展欧几里得算法逻辑,并且可以作为处理类似问题的基础工具函数调用。 #### 实际应用案例分析 回到原题本身,“A. 欧几里得”的解答思路就是先预处理斐波那契数列前若干项数值存入数组`a[]`内以便快速查询,之后针对每一次询问直接输出对应位置处两相邻元素之即可得出最终答案。这实际上巧妙运用到了广为人知的裴蜀定理——任意一对互质正整数都可由它们自身的倍数组合而成,而这里正是借助了这一性质简化了解决方案的设计过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值