首页 > 其他分享 >P6256 题解

P6256 题解

时间:2023-12-31 21:58:53浏览次数:30  
标签:insert 题解 sum P6256 fd && lines first

我认为,这道题是我学 OI 历史以来做过的最难写,最难受,最变态,最不可做,最怀疑人生的题。

然后还莫名其妙遇见了!

给出一种时间复杂度略劣于 ix35 的做法。因为本人码力不是很好,因此认为这道题讲讲代码写法也很必要。

题意就是给一些线段上戳洞,使得对于给定的一个区间 \([l,r]\),从无穷远到水平轴有一条通路。

Part 1:转化

首先,我们考虑戳洞的方案,不难发现如果我们只戳了下面的挡板却不戳上面的挡板不一定有什么用,这启发我们可以先对所有的挡板进行处理,使得有一种戳洞的顺序是合理的。感性理解,我们不能单纯按横坐标排序,这样错得离谱。另一方面,对于这种涉及图的,还是偏序关系的,我们考虑拓扑排序。

然后考虑怎么建图。第一个想法是按照纵坐标从上到下连边,但这样横坐标的处理就迷惑了起来。我们只好采取扫描线类似思想,从左到右扫描,对于每个挡板的端点,对 set 中的前驱和后继分别连边,不仅保证了偏序关系,还减少了不必要的边。

第一步就结束了,对于我们建的图,跑一边拓扑排序,可以得到一个挡板序列。代码如下:

// 读入&&建边
    cin >> l >> r >> n;
    for (int i = 1; i <= n; i++)
        lines[i].read();
    sort(lines + 1, lines + 1 + n, cmp);
    l <<= 1, r <<= 1;
    for (int i = 1; i <= n; i++)
    {
        lines[i].id = i;
        lines[i].enlarge();
    }
    set<Line> s;
    priority_queue<pii> q;
    for (int i = 1; i < n; i++)
    {
        while (q.size() && -q.top().first < lines[i].a)
            s.erase(lines[q.top().second]), q.pop();
        auto it = s.lower_bound(lines[i]);
        if (it != s.begin())
            e[i].push_back(prev(it)->id), ++deg[prev(it)->id];
        if (it != s.end())
            e[it->id].push_back(i), ++deg[i];
        s.insert(lines[i]);
        q.push({-lines[i].c, i});
    }

按照题解所说,模拟即可。

// 拓扑
    queue<int> q;    
    for (int i = 1; i <= n; i++)
        if (!deg[i])
            q.push(i);    
    while (q.size())
    {
        int u = q.front();
        q.pop();
        //这里应该有一大段代码
        for (auto v : e[u])
            if (!(--deg[v]))
                q.push(v);
    }

这是很标准的拓扑。

Part 2:DP

转化为序列以后就可以快乐 DP 了。先把整个区间分段便于处理。

我们定义,\(\operatorname{dp}(i,j)\) 表示前 \(i\) 个挡板处理完,落在第 \(j\) 个区间所需要戳的最少的洞的个数,然后考虑转移。

首先,这东西倒着做比正着做好做得多。因此来倒着转移。

显然转移要沿着线段转移。对于一条左低右高的线段,如果我们想要落在这个线段的区间的话,显然应该在上边戳个洞,那么我们直接对这一段区间 \((l,r]+1\)。那么对于左高右低的线段同理,也是转化成区间操作。具体的不在赘述。总之是显然可做的。

// DP 部分,塞在上文所留的空里
if (lines[u].b > lines[u].d)
        {
            auto it = f.lower_bound(pii(lines[u].a + 1, -inf));
            int sum = 0;
            vector<set<pii>::iterator> vec;
            for (; it != f.end() && it->first <= lines[u].c; ++it)
            {
                sum += it->second;
                vec.push_back(it);
                auto fd = g.lower_bound(pii(it->first, -inf));
                if (fd == g.end())
                    continue;
                while (fd != g.end() && sum + fd->second >= 0 && fd->first <= lines[u].c)
                {
                    sum += fd->second;
                    tmp = next(fd);
                    g.erase(fd);
                    fd = tmp;
                }
                if (sum > 0 && fd != g.end() && fd->first <= lines[u].c)
                {
                    int p = fd->first;
                    sum += fd->second;
                    g.erase(fd), g.insert(pii(p, sum));
                    sum = 0;
                }
            }
            for (auto fd : vec)
                f.erase(fd);
            if (sum > 0)
                f.insert(pii(lines[u].c + 1, sum));
            f.insert(pii(lines[u].a, 1)), g.insert(pii(lines[u].c, -1));
        }
        

因为篇幅原因,另一种情况的就不给出了。实际上一点点迭代模拟即可。

Part 3:统计答案和时间复杂度

考虑区间赋值的操作,在线段树上做前缀后缀 \(\min\) 是很容易的,操作几次以后就不变了,因此可以直接区间赋值了。更具体的,我们考虑维护区间单增单减,利用 segment beats 的理论我们直接把这个操作变成可赋值的操作。
最后统计答案的时候,我们只需要遍历 set 去看所有处于边界的线段,对这样的线段 ans 取 \(\min\) 即可!

时间复杂度用势能分析,修改增加 \(\log n\) 的势能,每点势能的减少的时间复杂度为 \(O(\log n)\),总的时间复杂度是 \(O(n\log^2n)\)。

    int ans = INT_MAX, sum = 0;
    f.insert(pii(l, 0)), f.insert(pii(r, 0)), f.insert(g.begin(), g.end());
    for (auto it = f.begin(); it != f.end(); it++)
    {
        sum += it->second;
        if (next(it) == f.end() || it->first != next(it)->first)
        {
            if (it->first >= l && it->first <= r)
                ans = min(ans, sum);
        }
    }

这是统计答案部分的代码,和上文所说相符。
你需要注意的是,此题的 dp 是极不寻常的。别问我 dp 数组在哪里,在 set 里(bushi)。

另外,讨厌我使用的这台电脑,怎么大括号换行的。

标签:insert,题解,sum,P6256,fd,&&,lines,first
From: https://www.cnblogs.com/Piggy424008/p/17938059/solution-p6256

相关文章

  • P9438 题解
    对于一次询问,相当于在考虑整数\(\frac{n}{x}\)变为\(1\)的方案数。进一步的,这相当于给定一个数列\(c_0\cdotsc_k\),每一次可以减小任意位的任意值,但不能空选,问方案数,这里“空选”指的是不选任何一个数。先考虑允许空选的时候应该怎么做,令\(f(x)\)代表正好走了\(x\)步变......
  • P4528 题解
    这篇题解并不做任何形式上的理论推导,而是在于引导像我一样的蒟蒻,如何在遇到这样的题时,不会陷入数据结构暴力分别求三种形态的深渊里无法自拔。看到这道题我们的第一想法应该是把三种形态的数量都求出来,如果可以的话,这题马上就秒掉了。那么我们尝试着去求——比较简单的可能是高......
  • 一些数 题解
    首先我们考察LIS长度为\(n-1\)的数列的性质。可以发现,这必定是\(1,2,3,\cdots,n\)中拎出一个不听话的元素甩到其他地方去,剩下的元素依次补齐所构成的。这意味着,最多只有一个元素满足\(a_i-i\ge2\),更具体的,不考虑只对邻项交换的排列(即形如\(1,2,3,\cdots,i,i-1,\cdots,n\)),......
  • 自出题题解
    U288469Piggy算路程显然是简单贪心。黄。U306825Piggy数编号先推式子。令\(L(n,k)\)为最长区块长度为\(k\)的方案数,则\(Ans=\sum_{i=0}^n\limits{L(n,i)}\timesk\)。下面转为求\(L(n,k)\)。我们考虑可能的区块长度分别为多少。那么就相当于我们要枚举所有的长度在......
  • CF1438F 题解
    如果能想到这道题用随机化,想来这道题的解法就显然了。但是为什么这道题一定要随机呢?我们考虑一棵完美二叉树,编号随机。这棵树的熵毛估估一下应该是\(O(\log^nn)\)的,但是一次询问的话,考虑每次只能得到三个点的偏序关系为某几种情况的一种,这个熵是很小的,只有\(O(\logn)\),对减......
  • P7400 题解
    P7400,一个有趣的博弈论。下面称Paula和Marin都执行一轮操作的“一整轮”为一个周期。Sub1:\(n\le100\)我们采用\(O(n^2\timesn)=O(n^3)\)的DP即可。这里略去具体实现。Sub2:边的颜色均为洋红这意味着两人都可以走过任意一条边。考虑两方如何对对方进行“围追堵截”......
  • P9309 题解
    此题问\(\operatorname{lcm}(a\simb)\)的后导\(0\)个数。考虑\(\operatorname{lcm}\)相当于对唯一分解中的素数的指数取\(\max\),此题等价于:定义\(\operatorname{g}(x,y,z)\)在\([a,b]\)的所有整数中,分解出\(z\)的最高次幂是多少,那么\(ans=\min(\operatorname{g}......
  • CF1827F 题解
    不妨先考虑一个弱化版的问题,这个问题和原来的问题仅有一个区别:\(k\)是给定整数。称最后\(n-k\)个数是“特殊的”。那么我们可以注意到,每个特殊的数字的极大段必然递增放置或者递减放置。例如我们有排列\([7,5,8,1,4,2,6,3]\)而且\(k=2\),那么极大段的下标应该是\([1,4],[6,......
  • P4875 题解
    显然这道题的解法与\(8\)强相关。从这一点下手,我们不难想到先对每一种奶牛做前缀和,这样我们可以做到\(O(8)\)查询每个区间是否可行,从而有了一个\(O(4n^2)\)的纯暴力做法。不知道多少pts,反正不是正解。下一步我们考虑优化。如果我们能快速地找到哪些区间是合法的,那么时间复......
  • P5138 题解
    因为本题的代码难度远大于解法的思考,因此这里提供一种好写的写法。做法不再赘述,就是转化为\(depth\)差以后上线段树分别维护两个信息以后求和。题解中大多数使用同一个线段树维护两个信息,可读性并不高,且比较难写。事实上我们注意到两棵线段树仅有初始的信息不一样,剩下需要支持......