zoukankan      html  css  js  c++  java
  • 2019牛客暑期多校训练营(第十场)F-Popping Balloons

    >传送门<

    前言


     跟学知识一样,做题目的确是也是常做常新。这道题当时的确已经解决了,国庆前工作室学长还有其他人又提起这道题,我说不就暴力枚举贪心么,后来学长说那你不好证明贪心的正确性!我一想的确是这样的,基本上没有人讲这怎么三次贪心选取就可以了,有点像瞎搞的样子。于是就又去看了$multiset$的做法,其实用线段树也可以,作用都是较快的维护最大值,只要思维到位了,选择恰当的数据结构就能做出来。

    题意


     现在给你n个点 ,让你横着划三条线间距为r 然后竖着划三条线间距同样为r ,求经过最多的点数

    思路


    比赛看到这题的时候觉得能做,但是一看时间限制是5s,搞得我有不敢去碰了,就没有写,但是赛后再看其实并没有那么复杂。有的人是用线段树去写的,感觉线段树简直万能~其实直接求也是可以的,感觉有点暴力(就算是暴力我也想不到QAQ)

    下面介绍两者做法,一种是贪心枚举瞎搞,一种是用$multiset+$思维。

    贪心枚举(为什么说他瞎搞,这么做感觉是对的,也的确能做,但是你很难证明贪心的正确性,不过在比赛的时候也不失为一种好方法)

    正常思路肯定时枚举横的三线和竖的三线,找到最大,这样时O(n2)的复杂度。

    一般二维的题目我们枚举一维,比如枚举横三线,

    预处理竖三线,以每个x为三线的左线,求出三线包含的气球个数(cntX[i] + cntX[i + r] + cntX[i + r * 2],对竖三线包含的气球数进行从大到小排序,贪心进行选取

    然后枚举纵坐标,算出以当前纵坐标为底线的三横线包含的气球个数,由于这样会重复,所以对于横三线已经选取的点竖三线就不再进行选取,之后取最大值和竖三线的值相加就行。

    最后,按照这种贪心的取法就能得到最大值(对竖三线的值贪心三次就能得到,这是我没想到的,100会更保险一点)

    Code

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 10;
    int n, r, cntX[maxn*3], cntY[maxn*3], res;
    struct Point {
        int x, y;
        bool operator<(const Point &rhs)const {
            return x > rhs.x;
        }
    } a[maxn], sum[maxn];
    
    int main() 
    {
        scanf("%d%d", &n, &r);
        for (int i = 0; i < n; i++) {
            scanf("%d%d", &a[i].x, &a[i].y);
            cntX[a[i].x]++;
        }
        for (int i = 0; i < maxn; i++) {
            sum[i].x = cntX[i] + cntX[i + r] + cntX[i + r * 2];
            sum[i].y = i;
        }
        sort(sum, sum + maxn);
        for (int i = 0; i < 100; i++) {
            memset(cntY, 0, sizeof(cntY));
            for (int j = 0; j < n; j++)
                if (a[j].x != sum[i].y && a[j].x != sum[i].y + r && a[j].x != sum[i].y + r * 2)
                    cntY[a[j].y]++;
            int my = 0;
            for (int j = 0; j < maxn; j++)
                my = max(my, cntY[j] + cntY[j + r] + cntY[j + r * 2]);
            res = max(res, my + sum[i].x);
        }
        printf("%d
    ", res);
        return 0;
    }
    /*
    总体的思路就是分别对横纵坐标暴力枚举三条线经过的点数(去重),然后通过贪心的思想选取,最后出来的就是最大值
    首先直接用sum[i].x枚举三条竖线(i, i+r, i+2*r)能够经过的点数,sum[i].y记录当前枚举是从横坐标为i这条竖线开始的
    然后对sum[i]按x从大到小排序,贪心的选取sum[i]后,肯定不能直接枚举三条横线能够经过的点数,因为这样会有重复。
    那怎么办呢?我们就先对cntY[a[j].y]进行处理,假如我选取的sum[i]中已经包含a[j]这个点了,那么我的cntY[[a[j].y]就不能加1,因为这个点已经被计算过
    现在当然就可以枚举三条横线能够经过的点数,取最大值和sum[i].x相加就是当前总共能经过的最多点数
    */
    View Code

     

    $multiset+$思维

    官方题解说的已经很详细了,这种题其实很考验思维。

    我们用$f(i)$表示中间一枪打第$i$行,能够射中的气球个数,用$g(i)$表示中间一枪打第$i$列,能射中的气球个数。

    用$multiset$存所有$f(i)$的值,枚举中间一枪打第$x$列,将对每一个位于第$x-r,x+r$列的气球,将它们影响到的行(共三行)的$f(j)$的值更新,然后更新$multiset$内的元素。

    中间一枪打第$x$列的最大收益即$g(x)$+(当前$multiset$内最大元素)。

    Code

    #include<cstring>
    #include<string>
    #include<set>
    #include<vector>
    using namespace std;
    const int maxn = 1e5 + 10;
    const int M = 1e5;
    
    int cnt[maxn];
    vector <int> h[maxn];
    multiset<int> s;
    void add(int x) {
        auto p = s.find(cnt[x]);
        s.erase(p);
        cnt[x]++;
        s.insert(cnt[x]);
    }
    void del(int x) {
        auto p = s.find(cnt[x]);
        s.erase(p);
        cnt[x]--;
        s.insert(cnt[x]);
    }
    //multiset + 思维
    int main()
    {
        int n, r, x, y, ans = 0;
        scanf("%d%d", &n, &r);
        for (int i = 0; i < n; i++) {
            scanf("%d%d", &x, &y);
            h[x].push_back(y);
            if (x - r >= 0) h[x - r].push_back(y);
            if (x + r <= M) h[x + r].push_back(y);
            cnt[y]++;
            if (y - r >= 0) cnt[y - r]++;
            if (y + r <= M) cnt[y + r]++;
        }
        for (int i = 0; i <= M; i++) s.insert(cnt[i]);//往multiset里添加行的气球数
        for (int i = 0; i <= M; i++) {
            int res = (int)h[i].size();//三列的气球总数
            for (int j = 0; j < res ; j++) {//如果影响了三行,就更新
                del(h[i][j]);
                if (h[i][j] - r >= 0) del(h[i][j] - r);
                if (h[i][j] + r <= M) del(h[i][j] + r);
            }
            ans = max(ans, res + (int)(*s.rbegin()));//取最大值
            for (int j = 0; j < res ; j++) {//再更新回去
                add(h[i][j]);
                if (h[i][j] - r >= 0) add(h[i][j] - r);
                if (h[i][j] + r <= M) add(h[i][j] + r);
            }
        }
        printf("%d
    ", ans);
        return 0;
    }
    View Code
  • 相关阅读:
    CMS网站 中最好用的!
    成为优秀设计师的十大条件
    网站变色(黑白)!
    设计师必知的18种服装风格
    HDFS核心类FileSystem的使用
    Hadoop的伪分布式安装和部署的流程
    初学MapReduce离线计算(eclipse实现)
    hdfs的客户端读写流程以及namenode,secondarynamenode,checkpoint原理
    hadoop常用的操作指令
    TableLayoutPanel&SplitContainer 布局
  • 原文地址:https://www.cnblogs.com/wizarderror/p/11377353.html
Copyright © 2011-2022 走看看