码迷,mamicode.com
首页 > 编程语言 > 详细

[CSP校内集训]pestc(拓扑排序)

时间:2019-11-06 15:03:00      阅读:90      评论:0      收藏:0      [点我收藏+]

标签:name   解法   erase   拓扑   stdout   push   pop   返回   http   

题意

给一个边带权的有向图,可以花费边权使得一条边反向;通过翻转边让原图变成一个DAG,要求是所有花费中的最大值最小\(,(n,m\leq 200000)\),保证无重边和自环

解法1

考场上没看出来性质,于是口胡了一个乱搞做法

连好边后直接对原图进行一遍拓扑排序,由于原图不是DAG,所以会有无法入队的环存在;如果当前队列为空而有点没有被遍历到,那么就强行选择一个点将连向它的边翻转;
具体的,我们选择\((max(\) 连向\(i\)的边 \())\)最小的\(i\),由于翻转了连向\(i\)的边,需要将\(ans\)\((max(\) 连向i的边 \())\)取最大值

维护max值用大根堆,维护max值最小的点用set,虽说复杂度为\(O(nlogn)\)但并不好写(为什么还跑的比正解快啊qwq)

Code

#include<bits/stdc++.h>
#define N 200005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,m,rd[N],ans=0;
bool vis[N];
struct Edge
{
    int next,to,dis;
}edge[N<<1];int head[N],cnt;
void add_edge(int from,int to,int dis) 
{
    edge[++cnt].next=head[from];
    edge[cnt].to=to;
    edge[cnt].dis=dis;
    head[from]=cnt; 
}
priority_queue<int> mx[N];//一个大根堆维护指向一个点最大的边权 
priority_queue<int> lz[N];//一个大根堆懒删除
set< pair<int,int> > s;//一个set维护最大边权最小的点编号 

template <class T>
void read(T &x)
{
    char c; int sign=1;
    while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void topo()//一次拓扑 
{
    queue<int> q;
    for(int i=1;i<=n;++i) 
      if(!rd[i]) q.push(i);
        else s.insert(make_pair(mx[i].top(),i));
    int rest=n;
    while(rest)
    {
        --rest;
        int v,u;
        if(q.empty())//成环了 
        {
            v=s.begin()->first,u=s.begin()->second;
            while(!s.empty()&&vis[u])
            {
                s.erase(make_pair(v,u));
                v=s.begin()->first;u=s.begin()->second;
            }
            ans=Max(ans,v);
        }
        else {u=q.front();q.pop();}
        vis[u]=1;
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(--rd[v]==0 && !vis[v]) {q.push(v);vis[v]=1;continue;}
            //如果没有入队就更新 
            lz[v].push(edge[i].dis);//删除这条边 
            s.erase(make_pair(mx[v].top(),v));//更新最大值 
            while(!lz[v].empty()&&mx[v].top()==lz[v].top())
            {
                mx[v].pop();
                lz[v].pop();
            }
            s.insert(make_pair(mx[v].top(),v));
        }
    }
}
int main()
{
    freopen("pestc.in","r",stdin);
    freopen("pestc.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=m;++i) 
    {
        int x,y,z;
        read(x);read(y);read(z);
        add_edge(x,y,z);
        mx[y].push(z);
        ++rd[y];
    }
    topo();
    cout<<ans<<endl;
    return 0;
}

解法2

题解做法

显然答案具有单调性,二分一个\(mid\),将\(\leq mid\)的边全部删掉,如果此时的图是一个DAG那么就返回true

正确性:由于此时的图是一个DAG,而\(\leq mid\)的边方向可以随便定,从dfn小的点指向dfn大的点即可形成一个新的DAG


原题CF1100E Andrew and Taxi还需要输出翻转边的方案,用解法2通过比较dfn可以很容易输出方案;而解法1还需要再在大根堆中记录每条边的编号emmm

[CSP校内集训]pestc(拓扑排序)

标签:name   解法   erase   拓扑   stdout   push   pop   返回   http   

原文地址:https://www.cnblogs.com/Chtholly/p/11805034.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!