首页 > 其他分享 >P3243 [HNOI2015] 菜肴制作 题解

P3243 [HNOI2015] 菜肴制作 题解

时间:2024-01-13 17:35:34浏览次数:34  
标签:dots 菜肴 int 题解 拓扑 mi HNOI2015 P3243 复杂度

前言

今天考试考到这道题,挂惨了。

题意

有 \(n\) 道菜肴,编号为 \(1 \sim n\)。有 \(m\) 个条件,形如 \((i, j)\),表示菜肴 \(i\) 必须在菜肴 \(j\) 之前制作。需求出一个菜肴的制作顺序,满足:

  1. 在满足所有限制的前提下,\(1\) 号菜肴尽量优先制作。

  2. 在满足所有限制,\(1\) 号菜肴尽量优先制作的前提下,\(2\) 号菜肴尽量优先制作。

  3. \(\dots\dots\)

  4. 以此类推。

如果无解,输出 Impossible!

形式化

给定 \(n\) 个点,\(m\) 条边的有向图,需求出其拓扑序,满足在拓扑序合法的前提下,点 \(1\) 优先且点 $i(i \in |V|, i \ne 1) $ 在点 \(i - 1\) 优先的前提下优先。无解输出 Impossible!

解法

首先给出一个错解,这东西是我考场胡的,但是可能对后面有帮助。

对于每一个点 \(x\),求出它往后的所有路径上经过的点的最小权值 \(mi_x\)(包括 \(x\))。然后使用堆进行拓扑排序,以 \(mi_x\) 为第一关键字,\(x\) 为第二关键字,就像这样:

它的正确拓扑序为:\(1, 3, 4, 5, 2\)。

但是,它是错误的,不管是正确性还是时间。

我们来看这个例子:

在这个图中,正确的拓扑序为 \(6, 5, 2, 4, 3, 1\),可这种方法跑出来的答案是 \(6, 4, 3, 5, 2, 1\)。可以发现,\(5\) 和 \(4\) 这两个点的 \(mi\) 是相等的,并且有公共的终点。这个时候,只要把两条路径中间的点调一下,这样贪心就是错误的。

关于时间,我采用的是 DFS,对于每个入度为 \(0\) 的节点,跑一遍 DFS,但是这样的最坏时间复杂度为 \(\Theta(\frac{n^2}{4})\),直接被卡飞。

我们可以对这个错解修改一下。

对于每个点 \(x\),它的子节点为 \(y_1, y_2, \dots, y_k\),在拓扑序中选择的先后顺序是这样的:将 \(y_1, y_2, \dots, y_k\) 往后的路径经过的点排序,然后按这些序列的字典序大小来选择。

按照刚刚的错解,错误的原因是多条路径上的最小值可能相等。那就一个一个从小到大比较啊,这样就是对的了。

这样做的朴素时间复杂度为 \(\Theta(n^2)\),但是可以使用 bitset 优化一下,可以将时间复杂度降为 \(\Theta(\frac{n^2}{w})\),算了一下单次大概 \(3 \cdot 10^7\) 的样子,数据组数小于等于 \(3\),那么就是 \(9 \cdot 10^7\),稍微卡一卡即可通过。

接下来讲时间复杂度正确的算法。

可以发现,我们刚才的分析都是正着分析的,但是这样是不好维护的,因为字典序小不一定是最优解。

那么我们倒着想。小的要在前面,那么大的就要在后面。在拓扑序合法的情况下,尽量把大的放在后面。那么这样就可以把小的数都尽量靠前。

那么,只需要在反图上跑字典序最大的拓扑序,然后反转一下即为答案。

字典序最大的拓扑序可以使用大根堆。

AC Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int t, n, m, in[N], IN[N], mi[N];
vector<int> e[N], ans;
void init()
{
    ans.clear();
    for(int i = 1; i <= n; i++)
        e[i].clear();
    memset(in, 0, sizeof(in));
    memset(IN, 0, sizeof(IN));
    // memset(mi, 0x3f, sizeof(mi));
    return;
}
void topo_sort()
{
    priority_queue<int> q;
    for(int i = 1; i <= n; i++)
        if(!in[i])
            q.push(i);
    int cnt = 0;
    while(!q.empty())
    {
        int x = q.top();
        q.pop();
        cnt++;
        ans.push_back(x);
        for(auto y : e[x])
            if(!--in[y])
                q.push(y);
    }
    if(cnt != n)
    {
        cout << "Impossible!\n";
        return;
    }
    reverse(ans.begin(), ans.end());
    for(auto i : ans)
        cout << i << " ";
    cout << "\n";
    return;
}
void solve()
{
    cin >> n >> m;
    init();
    for(int i = 1; i <= m; i++)
    {
        int x, y;
        cin >> x >> y;
        e[y].push_back(x);
        in[x]++;
    }
    // for(int i = 1; i <= n; i++)
    //     cout << mi[i] << " ";
    // cout << "\n";
    // for(int i = 1; i <= n; i++)
    //     for(int j = i + 1; j <= n; j++)
    //     if(!vis[i][j])
    //     {
    //         vis[i][j] = vis[j][i] = true;
    //         e[i].push_back(j);
    //         in[j]++;
    //     }
    topo_sort();
    return;
}
signed main()
{
    // freopen("dishes.in", "r", stdin);
    // freopen("dishes.out", "w", stdout);
    cin >> t;
    while(t--)
        solve();
    return 0;
}

标签:dots,菜肴,int,题解,拓扑,mi,HNOI2015,P3243,复杂度
From: https://www.cnblogs.com/Luckies/p/17962644/P3243_Solution

相关文章

  • P9007 [入门赛 #9] 最澄澈的空与海 (Hard Version) 题解
    Updon2023.10.1408:21:修改了推式子和题意的一些小错误。前言一道恐怖的绿题。显然我认为应该是蓝题。(不过在这篇题解写到一半的时候升蓝了,感谢@StudyingFather。)名字挺好的。题意给定\(n\),求出满足以下条件的三元组\((x,y,z)\)的组数:\(x\ge0,z\ge1\)。\(......
  • AT_abc243_g [ABC243G] Sqrt题解
    题目大意有一个数列,初始时只有一个数\(X\)。你可以对它进行一种操作:设末尾的数为\(Y\),从\(1\sim\sqrt{Y}\)中选一个数加到数列的末尾。如此进行\(10^{100}\)次操作,问数列一共有多少种可能的状态。解法考虑DP。设\(dp_i\)表示以数字\(i\)开头的数列可能的状态。设......
  • CF713D 题解
    题意给一个\(01\)矩阵,多次求在给定区间内最大的全\(1\)正方形边长。思路容易想到二分:先预处理出以每个位置为右下角的最大合法正方形的边长\(mx_{i,j}\),然后对于每个询问,我们二分边长\(mid\),设当前询问的区间左上角为\((x_1,y_1)\),右下角为\((x_2,y_2)\),则能取到\(mi......
  • AT_arc167_e 题解
    题意给定\(k\)和一个排列\(P'\),问有多少个排列\(P\)以最少步数交换相邻两个元素来进行收敛,最终的排列可能是\(P'\),一个排列是收敛的当且仅当对于每一个数,在该数前且比这个数大的数的个数不超过\(k\)个。思路考虑正向的让一个排列收敛,我们设在第\(i\)个位置前且比\(P......
  • AT_agc054_c 题解
    题意给定\(k\)和一个排列\(P'\),问有多少个排列\(P\)以最少步数交换相邻两个元素来进行收敛,最终的排列可能是\(P'\),一个排列是收敛的当且仅当对于每一个数,在该数前且比这个数大的数的个数不超过\(k\)个。思路考虑正向的让一个排列收敛,我们设在第\(i\)个位置前且比\(P......
  • P9754 题解
    题意不难理解,不多赘述。思路首先考虑对于性质A的情况,我们可以这样做:定义一个代表变量的结构体,里面存几个参数:首先肯定要存种类(\(type\))和名称(\(name\)),其次为了方便,我们把该变量的大小(\(siz\)),起始位置(\(fir\))和对齐要求(\(mx\))也存了。操作二\(type\),\(name\),\(siz\)和\(m......
  • AT_cf17_final_j 题解
    题意给定一棵既有点权也有边权的树,构造一个完全图,图中两点间边的边权为树中两点点权之和加上两点间的距离,求该图的最小生成树。思路发现完全图总边数太大,考虑减少边数。这里有一个性质:如果在一个图中选取任意个联通的边集,使得它们的并为全集,则整个图的最小生成树中的边一定在......
  • [AGC022F] Checkers 题解
    题目链接点击打开链接题目解法很妙的题!!!考虑\(x\)是无穷大的数,所以可以认为\(x^i\)的系数是单独的一项,不会和\(x^j(j\neqi)\)合并所以问题转化成了:每个数初始是\(x^i\)(\(x\)可以理解是元),进行题目中的操作,问最后形成的\(n\)次多项式的个数由\(B\)向\(A\)连边,这......
  • CF1006E Military Problem 题解
    CF1006EMilitaryProblem题解题意给定一颗有\(n\thinspace(2\leqn\leq2\times10^5)\)个节点的树,树根为\(1\)。对于每个节点\(i\thinspace(2\leqi\leqn)\)都有它的父节点\(p_i\),并且每个节点的子节点都是按从小到大的顺序排列的的。有\(q\thinspace(1......
  • 【题解】CatOJ C0458C 滑动窗口定期重构
    标题trick的名字我也不知道是什么,就这样吧。link。首先有显然的dp式子:\(f(i)=\min\{f(j)\times\max\{a_{j+1},\dots,a_i\}\}\)。考虑怎么去优化它。有显然的\(\mathcalO(n\logn)\):考虑线段树优化dp。用增的单调栈维护\(a\),若每次弹出顶部一个下标\(p\),则\([p+1,i......