洛谷 2 月月赛 I & 『MdOI R4』 (Div2) A ~ D 四题全,也许会有六题,超高质量题解 (Div.1E、F下辈子一定补)【每日亿题2 / 9】

整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


这场月赛就离谱

Div1,一千多人参加,除了两位大佬两题以外,其他人最多一题…
C题可并堆,D题分数规划,E题PQ树,F题生成函数NTT,离谱

A、P7337 『MdOI R4』Fun

Problem A. Fun

VG 的学校有 n n n 个人要去考 NOIP。

每个人有一个交通方式,第 i i i 个人的交通方式为 t i t_i ti t i = 1 t_i=1 ti=1表示这个人坐学校大巴, t i = 0 t_i=0 ti=0 表示这个人自己去考场。

每个人有一个颓废值,第 i i i 个人的颓废值为 q i q_i qi q i = 1 q_i=1 qi=1表示这个人愿意打狼, q i = 0 q_i=0 qi=0 表示这个人不愿意打狼。

每个人去考场时会买一瓶快乐水,但如果坐大巴且愿意打狼的人数(即满足 t i = 1 t_i=1 ti=1 q i = 1 q_i=1 qi=1 i i i 个数) k k k 不小于 m m m ,则这 k k k 个人只需要买 m m m 瓶快乐水。

现在,VG 统计出了所有人的交通方式和颓废值,他希望请求你帮他求出最终所有人买快乐水的总瓶数。

Soluiotn

签到题,读懂题意直接模拟就行了(我看到题还楞了一下,怎么这么简单???

可能是因为数据太少,我用快读快输还没有直接scanf跑的快…

时间复杂度: O ( n ) O(n) O(n)
Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

const int N = 5007;

int n, m;
int t[N], q[N];
int type;

int main()
{
    
    
    scanf("%d%d%d", &n, &m, &type);
    for(int i = 1; i <= n; ++ i) {
    
    
        scanf("%d", &t[i]);
    }
    for(int i = 1; i <= n; ++ i) {
    
    
        scanf("%d", &q[i]);
    }
    int k = 0;
    for(int i = 1; i <= n; ++ i) {
    
    
        if(t[i] && q[i]) {
    
    
            k ++ ;
        }
    }
    int ans = n - k;
    if(k >= m) {
    
    
        ans += m;
    }
    else ans += k;
    printf("%d\n", ans);
    return 0;
}

B、P7338 『MdOI R4』Color

Problem B. Color

小 M 同学有一张 2 2 2 n n n 列的方格纸,一开始所有格子都是白色的。

她决定对一些格子染色,具体地,每次她会选择两个相邻的(四联通的,也就是有公共边的)白色格子,其中一个染成红色,另一个染成蓝色。

她的目标是通过任意次操作让指定的一些格子变成红色,对其他格子没有要求。请你帮她判断一下,能否通过上述操作达成目标呢?

Soluiotn

简单画图模拟以后,直接贪心即可。

我直接乱搞疯狂分类讨论疯狂贪心然后就过了…因为只有两行,所以情况不会太多 ~

好吧我这个思路其实就是正解。

不过我见过有人用Dinic满分来着hhh
还有用匈牙利跑二分图匹配99分的hhh

简单讲一下思路:题目要求每次染色的时候必须选择两个白色格子,也就是只能选择没有染过色的格子,然后给我们一个期望染色的方案,并且没有标 1 是 0 的格子的颜色是不做要求的。

所以我们就可以直接根据它给定的期望方案去染色,对于每一个1来说,因为只有两行,所以我们分类讨论(若有 n n n m m m 列就是经典的网络流最大匹配了 ~ )

根据题意只有相邻的才能选上一块染色,所以我们只需要考虑每个1的上下左右,也就是上下行和前后列,优先考虑前面一列,然后考虑上下以及后面的只有一个0的情况,若没有0直接 NO ,剩下最后一种情况就是下和后,或者上和后都可以选,我们优先考虑下或者上,也就是同一列的那个格子,因为同一列第 i i i 列的格子,之后 第 i + 1 i+1 i+1 列可以用到,而后一列即第 i + 1 i+1 i+1 的那个可选的格子, 第 i + 2 i+2 i+2 列也可以使用 ~ 然后我们模拟的时候染成红色的时候就置为 1 ,蓝色就置为 2 ,然后就没了

时间复杂度: O ( n ) O(n) O(n)

Code

然后就是我10分钟一通乱搞AC的丑陋的代码了 ~

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef int itn;
const int N = 5e5 + 7, M = 1e6 + 7, mod = 1e9 + 7;
const ll INF = 1e18 + 7;
ll n, m, t;
ll A, B;
string a, b;

bool solve()
{
    
    
    scanf("%d", &n);
    cin >> a >> b;
    for(int i = 0; i < n; ++ i) {
    
    
        bool flag1 = 0, flag2 = 0;
        if(a[i] == '1') flag1 = 1;
        if(b[i] == '1') flag2 = 1;
        if(flag1) {
    
    
            if(i != 0 && a[i - 1] == '0') {
    
    
                a[i - 1] = '2';
            }
            else if(i != n - 1 && a[i + 1] == '1' && b[i] == '0') {
    
    
                b[i] = '2';
            }
            else if(i != n - 1 && a[i + 1] == '0' && b[i] == '1') {
    
    
                a[i + 1] = '2';
            }
            else if(i != n - 1 && a[i + 1] == '1' && b[i] == '1') {
    
    
                return false;
            }
            else if(i == n - 1) {
    
    
                if(b[i] == '1') return false;
            }
            else if(b[i] == '0'){
    
    
                b[i] = '2';
            }
            else if(a[i + 1] == '0')
                a[i + 1] == '2';
            else return false;
        }
        if(flag2) {
    
    
            if(i != 0 && b[i - 1] == '0') {
    
    
                b[i - 1] = '2';
            }
            else if(i != n - 1 && b[i + 1] == '1' && a[i] == '0') {
    
    
                a[i] = '2';
            }
            else if(i != n - 1 && b[i + 1] == '0' && a[i] == '1') {
    
    
                b[i + 1] = '2';
            }
            else if(i != n - 1 && b[i + 1] == '1' && a[i] == '1') {
    
    
                return false;
            }
            else if(i == n - 1) {
    
    
                if(a[i] == '1') return false;
            }
            else if(a[i] == '0'){
    
    
                a[i] = '2';
            }
            else if(b[i + 1] == '0')
                b[i + 1] == '2';
            else return false;
        }
    }
    return true;
}

int main()
{
    
    
    scanf("%lld", &t);
    while(t -- ) {
    
    
        if(solve()) puts("RP");
        else puts("++");
    }
    return 0;
}

C、P7339 『MdOI R4』Kotori

Problem C. Kotori
在这里插入图片描述

琴里yyds
在这里插入图片描述

可并堆直接秒 (雾

我们的琴里可以在每一轮的比赛当中帮助任意一个人 +   m +\ m + m 票,所以我们可以模拟这个过程,因为整个比赛过程是 k 轮,每轮每次都是两个人比赛,也就是左区间和右区间。那么我们直接按照题意模拟比赛,实际上总复杂度只有 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n) ,是可以通过本题的。具体的模拟思路看代码会更好理解一些。所有不能获胜的都已经被筛掉了,剩下的都是可能获胜者。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef int itn;
const int N = 5e6 + 7, M = 1e6 + 7, mod = 1e9 + 7;
const int INF = 1e9 + 7;

int n, m, t, k, q;
int a[N], maxx;
int vis[N];

void solve()
{
    
    
    maxx = -INF;
    scanf("%d%d", &k, &m);
    n = (1 << k);
    for(int i = 1; i <= n; ++ i) {
    
    
        scanf("%d", &a[i]);
        vis[i] = true;
    }
    for(int i = 1; i <= k; ++ i) {
    
    
        int step = 1 << i;//从两个人开始,模拟每轮的比赛
        for(int j = 1; j <= n; j += step) {
    
    
            int minL = INF;
            itn minR = INF;
            for(int pos = j; pos <= j + step / 2 - 1; ++ pos) {
    
    
                if(vis[pos]) {
    
    
                    minL = min(minL, a[pos]);
                }
            }
            for(int pos = j + step / 2; pos <= j + step - 1; ++ pos)  {
    
    
                if(vis[pos]) {
    
    
                    minR = min(minR, a[pos]);
                }
            }
            for(int pos = j; pos <= j + step / 2 - 1; ++ pos) {
    
    
                if(a[pos] + m < minR) {
    
     //连右边最菜的(你的对手)加上kotori的帮助都打不过,再见 ~
                    vis[pos] = false;
                }
            }
            for(int pos = j + step / 2; pos <= j + step - 1; ++ pos) {
    
    
                if(a[pos] + m < minL) {
    
     //连左边最菜的(你的对手)加上kotori的帮助都打不过,再见 ~
                    vis[pos] = false;
                }
            }
        }
    }
    if(vis[1]) {
    
    
        puts("Kotori");
    }
    else puts("Yoshino");
}

int main()
{
    
    
    scanf("%d", &t);
    while(t -- ) {
    
    
        solve();
    }
    return 0;
}

正解

最后我们发现整个比赛的过程实际上就是一个归并排序 ~

我们可以直接用归并排序解决。

每次删掉最小的不满足条件的人,然后对剩余的进行归并

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

在这里插入图片描述
约会大作战建议只看前两季(

不过好消息是第四季已经有消息啦!建议第三季直接补原作,不过确实,能动就行(

在这里插入图片描述
啥时候Yoshino(四系乃)也拿个世萌啊

D、P7340 『MdOI R4』Balance

Problem D Balance
在这里插入图片描述

Solution

我来分析一下这道题是怎么思考的吧。

首先我们要求的是一对点 ( i , j ) (i,j) (i,j) ,使得 f ( i , j ) f(i,j) f(i,j) f ( i , t ) f(i,t) f(i,t) 中是第 x x x 小,在 f ( s , j ) f(s,j) f(s,j) 中是第 y y y 小,也就是

a i + b j p i + q j ≥ f ( i , t ) v a l x t h \cfrac{a_i+b_j}{p_i+q_j}\ge f(i,t)val_{xth} pi+qjai+bjf(i,t)valxth

a i + b j p i + q j ≥ f ( s , j ) v a l y t h \cfrac{a_i+b_j}{p_i+q_j}\ge f(s,j)val_{yth} pi+qjai+bjf(s,j)valyth

那么我们肯定来考虑一个一般性的问题:

f ( i , j ) = a i + b j p i + q j ≥ v a l f(i,j)=\cfrac{a_i+b_j}{p_i+q_j}\ge val f(i,j)=pi+qjai+bjval

也就是如何得到一个通用的解法

然后可以发现这实际上就是一个01分数规划的模板结构。

我们对其进行分数规划:

将他乘开:

a i + b j ≥ ( p i + q j ) × v a l a_i+b_j\ge (p_i+q_j)\times val ai+bj(pi+qj)×val

合并同类项

a i − p i × v a l ≥ q j × v a l − b j a_i-p_i\times val\ge q_j\times val - b_j aipi×valqj×valbj

( a i − p i × v a l ) + ( b j − q j × v a l ) ≥ 0 (a_i-p_i\times val)+(b_j-q_j\times val)\ge 0 (aipi×val)+(bjqj×val)0

我们设

U i = a i − p i × v a l U_i=a_i-p_i\times val Ui=aipi×val

V j = b j − q j × v a l V_j=b_j-q_j\times val Vj=bjqj×val

U i + V j ≥ 0 U_i+V_j\ge 0 Ui+Vj0

我们发现 U i U_i Ui 仅与 i i i v a l val val 有关, V j V_j Vj 仅与 j j j v a l val val 有关,并且映射到图上, f ( i , j ) = a i + b j p i + q j ≥ v a l f(i,j)=\cfrac{a_i+b_j}{p_i+q_j}\ge val f(i,j)=pi+qjai+bjval f ( i , j ) = a i + b j p i + q j ≤ v a l f(i,j)=\cfrac{a_i+b_j}{p_i+q_j}\le val f(i,j)=pi+qjai+bjval 一定是严格分开的,中间有一个明显的分界线。

并且 U i U_i Ui V j V_j Vj 在固定的 ( i , j ) (i,j) (i,j) 时会随着 v a l val val 的变化而变化,所以我们可以很自然地 根据分数规划的套路 想出二分 v a l val val ,然后判断当前的 v a l = m i d val = mid val=mid 是否存在一个 U y + V x ≥ 0 U_y+V_x\ge 0 Uy+Vx0

至于为什么是 U y U_y Uy V x V_x Vx,根据题意我们想要找的是 f ( i , j ) f(i,j) f(i,j) f ( i , t ) f(i,t) f(i,t) 中是第 x x x 小, i i i 是固定的,也就是 U i U_i Ui 是固定的,我们排序后找到第二维 j j j 也就是 V j V_j Vj里的第 x x x 小值。即在 V V V 中找第 x x x 小值,同理,在 U U U 中找第 y y y 小值。

但是我们这里只是想要找到整个序列的第 x x x 小元素以及第 y y y 小的元素,所以我们可以直接使用 nth_element 期望 O ( n ) O(n) O(n) 线性地找到,而不需要暴力排序之后 O ( n l o g n ) O(nlogn) O(nlogn) 找到。

简单介绍一下 nth_element

nth_element(a, a + n, a + len);

数组长度为 len,该函数的作用是使第 n n n 大元素处于第 n n n 位置(从 0 0 0 开始,其位置是下标为 n n n 的元素),并且比这个元素小的元素都排在这个元素之前比这个元素大的元素都排在这个元素之后,但不能保证他们是有序的。如果是自己手写的结构体,也可以在 nth_element 的最后面加上比较函数 cmp

大量数据随机访问下 nth_element O ( n ) O(n) O(n) 的。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

对不起我讲的有点乱,下午我再梳理一下
/kk

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>

using namespace std;
typedef long long ll;
typedef int itn;
const int N = 5e6 + 7, mod = 1e9 + 7;
const double INF = 1e9 + 7;

int n, m, t;
int x, y;

struct node
{
    
    
    itn id, a, q;
    int b, p;
    double val;
}a[N], b[N];

bool cmp(node a, node b) // 因为我们找的是第 x 小,所以要重载比较运算符
{
    
    
    return a.val < b.val;
}

bool check(double val)
{
    
    
    for(int i = 1; i <= n; ++ i) {
    
    
        a[i].val = a[i].a - val * a[i].p;
        b[i].val = b[i].b - val * b[i].q;
    }

    nth_element(a + 1, a + y, a + 1 + n, cmp);
    nth_element(b + 1, b + x, b + 1 + n, cmp);

    if(a[y].val + b[x].val >= 0)
        return true;
    return false;
}

void solve()
{
    
    
    scanf("%d%d%d", &n, &x, &y);
    for(int i = 1; i <= n; ++ i) {
    
    
        scanf("%d%d%d%d", &a[i].a, &b[i].b, &a[i].p, &b[i].q);
        a[i].id = i;
        b[i].id = i;
    }
    double l = -INF, r = INF;
    for(int i = 1; i <= 60; ++ i) {
    
    
        double mid = (l + r) / 2;
        if(check(mid)) l = mid;
        else r = mid;
    }
    printf("%d %d\n", a[y].id, b[x].id);//nth_element 已经帮我们把答案放到正确的位置了 ~
    return ;
}

int main()
{
    
    
    scanf("%d", &t);
    while(t -- ) {
    
    
        solve();
    }
}

E、P7341 『MdOI R4』Phoenix

PQ - Tree 可过,下辈子一定写(

F、P7342 『MdOI R4』Destiny

推一下式子,生成函数 + NTT 可过,下辈子一定写(

猜你喜欢

转载自blog.csdn.net/weixin_45697774/article/details/113774546