【HDU - 3251 】 Being a Hero 【最小割建图--输出割边】

本文介绍了一种利用最小割算法解决特定城市选择问题的方法。该问题要求玩家选择不可从首都直接到达的城市,并需要销毁某些道路以确保这些城市的隔离。文章详细解释了如何构建图模型、确定最小割及计算最佳收益。

You are the hero who saved your country. As promised, the king will give you some cities of the country, and you can choose which ones to own!

But don’t get too excited. The cities you take should NOT be reachable from the capital – the king does not want to accidentally enter your area. In order to satisfy this condition, you have to destroy some roads. What’s worse, you have to pay for that – each road is associated with some positive cost. That is, your final income is the total value of the cities you take, minus the total cost of destroyed roads.

Note that each road is a unidirectional, i.e only one direction is available. Some cities are reserved for the king, so you cannot take any of them even if they’re unreachable from the capital. The capital city is always the city number 1.
Input
The first line contains a single integer T (T <= 20), the number of test cases. Each case begins with three integers n, m, f (1 <= f < n <= 1000, 1 <= m < 100000), the number of cities, number of roads, and number of cities that you can take. Cities are numbered 1 to n. Each of the following m lines contains three integers u, v, w, denoting a road from city u to city v, with cost w. Each of the following f lines contains two integers u and w, denoting an available city u, with value w.
Output
For each test case, print the case number and the best final income in the first line. In the second line, print e, the number of roads you should destroy, followed by e integers, the IDs of the destroyed roads. Roads are numbered 1 to m in the same order they appear in the input. If there are more than one solution, any one will do.
Sample Input
2
4 4 2
1 2 2
1 3 3
3 2 4
2 4 1
2 3
4 4
4 4 2
1 2 2
1 3 3
3 2 1
2 4 1
2 3
4 4
Sample Output
Case 1: 3
1 4
Case 2: 4
2 1 3

分析: 建图: 先按照题中给的边建好之后,然后再将所有可以选择的点,以边权为得到的费用和汇点相连接。
为什么这样就可以满足题意呢? 如果要是割与汇点相连接的边,那么就说明 这条边所代表的城市 不能够选择,因为不能够通过其余的边来阻断,说明用其余的边来阻断没有截断 这个城市省钱,这就说明 通过割段别的边来阻断的花费 要超过得到的钱。
求最小割:S-T
然后 从S跑一遍,遍历得到所有 S集合的点,然后枚举所有S集合的点,如果有指向T集合的点的边,那么这条边一定为割边。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
#define LL long long

const int N = 1000*2+11;
const int M = 1e6+11;
const int inf = 0x3f3f3f3f;

struct Edge {
    int form,to,cap,flow,nexts,id;
}edge[M];
int head[N],top;
void init(){
    memset(head,-1,sizeof(head));
    top=0;
}
void addedge(int a,int b,int c,int id){
    Edge e={a,b,c,0,head[a],id};
    edge[top]=e;head[a]=top++;

    Edge ee={b,a,0,0,head[b],id};
    edge[top]=ee;head[b]=top++;
}
int vis[N],dis[N];
int cur[N];
bool bfs(int st,int ed){
    queue<int>Q;
    memset(vis,0,sizeof(vis));
    memset(dis,-1,sizeof(dis));
    Q.push(st);vis[st]=1;dis[st]=1;
    while(!Q.empty()){
        int now=Q.front();Q.pop();
        for(int i=head[now];i!=-1;i=edge[i].nexts){
            Edge e=edge[i];
            if(!vis[e.to]&&e.cap-e.flow>0){
                vis[e.to]=1;
                dis[e.to]=dis[now]+1;
                if(e.to==ed) return 1;
                Q.push(e.to);
            }
        }
    }
    return 0;
}

int dfs(int now,int a,int ed){
    if(a==0||now==ed) return a;
    int flow=0,f;
    for(int &i=cur[now];i!=-1;i=edge[i].nexts){
        Edge &e=edge[i];
        if(dis[e.to]==dis[now]+1&&(f=dfs(e.to,min(e.cap-e.flow,a),ed))>0){
            e.flow+=f;
            flow+=f;
            edge[i^1].flow-=f;
            a-=f;
            if(a==0) break;
        }
    }
    return flow;
}

int max_flow(int st ,int ed){
    int flow=0;
    while(bfs(st,ed)){
        memcpy(cur,head,sizeof(head));
        flow+=dfs(st,inf,ed);
    }
    return flow;
}

void Find(int now){
    vis[now]=1;
    for(int i=head[now];i!=-1;i=edge[i].nexts){
        Edge e=edge[i];
        if(!vis[e.to] && e.cap!=e.flow) Find(e.to);
    }
}
vector<int>ve;
int main(){
    int t;scanf("%d",&t); int cas=1;
    while(t--){
        init();
        int n,m,f;scanf("%d%d%d",&n,&m,&f);
        int S =1,T=n+1;
        for(int i=1;i<=m;i++){
            int a,b,c;scanf("%d%d%d",&a,&b,&c);
            addedge(a,b,c,i);
        }

        int ans=0;
        for(int i=1;i<=f;i++){
            int a,b;scanf("%d%d",&a,&b);
            addedge(a,T,b,m+1);
            ans+=b;
        }

        ans-=max_flow(S,T);
        printf("Case %d: %d\n",cas++,ans);
        memset(vis,0,sizeof(vis));
        Find(S);
        ve.clear();
        for(int i=0;i<top;i+=2 ){
            Edge e=edge[i];
            int cnt=0;
            if(vis[e.form] && !vis[e.to] && e.id<=m){// 从S集合指向T集合的边
                ve.push_back(e.id);
            }
        }
        printf("%d",ve.size());
        for(int i=0;i<ve.size();i++){
            printf(" %d",ve[i]);
        }
        puts("");
    }
return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值