拓扑排序+并查集

图论专题M题
题意主要是:给出两个值大小关系,每个值至少为1,求出值的总和最少为多少,如果有矛盾输出-1
这道题之所以记下来,主要是处理图的方式是比较经典的,我觉得值得参考。
我一开始想到spfa,又因为这是多源的,所以采用了拓扑排序求最长路。
用并查集维护相等关系,最后对每个值取同一集合中最大值。
如果存在环,说明出现矛盾。
但是相等关系出现矛盾怎么维护呢?这个就是关键点了,对于1->3->5,1<3<5,如果5<1会构成环自然不对。但是1和5相等也是不对的,这个怎么判断呢,我们可以尝试让1和5连在一起也变成环,处理方式就是并查集缩点。
最后计算答案find找到祖先即可。
并查集缩点是一个比较偏僻但是关键的知识点,需要好好记住。处理出现矛盾的相等关系很有用。

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

#define ll long long
#define inf 0x3f3f3f3f

const int maxn=40200;
const int maxm=300050;

int n,m;

struct Edge{
    int from,to,dist;
    Edge(){}
    Edge(int _from,int _to,int _dist):from(_from),to(_to),dist(_dist){}
};
Edge ed[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,int w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}

int ind[maxn];
ll d[maxn];
queue<int>q;

bool topo() {
    for (int i = 1; i <= n; i++) {
        d[i]=1;
        if (!ind[i])q.push(i);
    }
    int cnt=0;
    while (!q.empty()) {
        cnt++;
        int now = q.front();q.pop();
        for (int i = he[now]; i; i = ne[i]) {
            Edge &e = ed[i];
            d[e.to] = max(d[e.to], d[now] + 1);
            if (!--ind[e.to]) q.push(e.to);
        }
    }
    if(cnt==n)return true;
    else return false;
}

int f[maxn];
int find(int x){return f[x]==x?x:(f[x]=find(f[x]));}
bool query(int x,int y){return find(x)==find(y);}
void uni(int x,int y){f[find(x)]=find(y);}
int mark[maxn],A[maxn],B[maxn];

int main(){
    cin>>n>>m;
    FOR(i,1,n)f[i]=i;
    bool ok = true;
    memset(ind,0,sizeof(ind));
    memset(d,0,sizeof(d));
    FOR(i,1,m){
        scanf("%d%d%d",&mark[i],&A[i],&B[i]);
        if(mark[i]==3){
            if(!query(A[i],B[i])){
                uni(A[i],B[i]);
            }
        }
    }
    FOR(i,1,m){
        if(mark[i]!=3){
            int x=find(A[i]);
            int y=find(B[i]);
            if(mark[i]==1)insert(y,x,1),ind[x]++;
            if(mark[i]==2)insert(x,y,1),ind[y]++;
        }
    }
    if(topo()){
        ll ans=0;
        FOR(i,1,n){
            ans+=d[find(i)];
        }
        cout<<ans<<endl;
    }
    else puts("-1");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值