思维并查集/网络流和二分

http://poj.org/problem?id=3228

题意:就是图上有n个点,m条边,题目给的俩个长度为n的序列,第一个是表示i位置的宝藏的体积V,第二个序列是表示i位置能容纳的宝藏体积V。然后给出m条边和权值。最终要问把这些宝藏全部放置在仓库所要走的路径中哪一条边最大(要这个最大值尽可能小)

并查集做法:

分析:首先想到的是,要能运完所有的金子,那么必须任意一个连通器中的储藏室的总容量V大于等于所含有的金子的总量C,也就是V-C>=0。那么就想到用一个数组存储该镇的状态,若是储藏室,则权值为储藏的量;若为发现金子的地方,则权值为金子的量的负值。这样每次合并的时候,都要更新一下父节点,即父节点的权值要加上子节点的权值。最后根节点的权值即为该集合中V-C的值。

        若根节点的权值为非负,表明该点集中,可以储存宝藏的容量大于等于发现的宝藏,即这个点集能满足要求。

   若根节点的权值为负值,表明该点集中还有一定数量的宝藏需要储存,但是该集合已经没有房间能存储下了,即不能满足条件。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=205;
struct node{
    int u,v,w;
    bool operator<(const node & b)const{
        return w<b.w;
    }
}e[N*N];
int fa[N],f[N],n;//f[i]表示第i个镇所拥有的宝藏,如果为负值,表明此镇需要运出-f[i]的宝藏。如果为正值,表明可以储存f[i]的宝藏
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
//判断是否所有集合都满足根节点的权值大于等于0
bool check(){
    for(int i=1;i<=n;i++){
        if(fa[i]==i&&f[i]<0)
            return false;
    }
    return true;
}
void init(){
    for(int i=1;i<=n;i++)
        f[i]=0,fa[i]=i;
}
int main(){
    while(~scanf("%d",&n)){
        if(n==0)
            break;
        init();
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            f[i]-=x;//需要运出的宝藏
        }
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            f[i]+=x; //可以储存的宝藏,因为发现宝藏和储存宝藏的地方可能为同一个,所以这里累加就行
        }
        int m;
        scanf("%d",&m);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
        }
         //从最小的边开始取
        sort(e+1,e+1+m);
        int i=1,ans=0;
        while(i<=m){
            int a=find(e[i].u),b=find(e[i].v);
            if(a!=b){
                fa[b]=a;
                f[a]+=f[b];
                if(check()){
                    ans=i;
                    break;
                }
            }
            i++;
        }
        if(i==m+1){
            puts("No Solution");
        }
        else
            printf("%d\n",e[ans].w);
    }
    return 0;
}
View Code

网络流做法:

分析:对于每个有金矿的点,源点向其连一条容量为金矿数的边,对于每个仓库,向汇点连一条容量为仓库储存量的边,然后对于所有边长不大于当前枚举的值的边,连双向的容量为无穷的边

   如果满流,则该枚举的边长可行,继续缩小,否则,枚举更大的边长

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
const int M=1e3+3;

struct node{
    int v,w,nextt;
}e[M*M];
struct NODE{
    int u,v,w;
    bool operator<(const NODE &b)const{
        return w<b.w;
    }
}g[M*M];
int n,m,s,t,gsum,ssum,tot,a[M*M],head[M*M],cur[M*M],deep[M*M],gold[M],store[M];
void addedge(int u,int v,int w){
    e[tot].v=v;
    e[tot].w=w;
    e[tot].nextt=head[u];
    head[u]=tot++;
    e[tot].v=u;
    e[tot].w=0;
    e[tot].nextt=head[v];
    head[v]=tot++;
    
}
bool bfs(){
    for(int i=0;i<=t;i++)
        deep[i]=0;
    queue<int>que;
    que.push(s);
    deep[s]=1;
    while(!que.empty()){
        int u=que.front();
        que.pop();
        for(int i=head[u];~i;i=e[i].nextt){
            int v=e[i].v;
            if(e[i].w>0&&deep[v]==0){
                deep[v]=deep[u]+1;
                if(v==t)
                    return true;
                que.push(v);
            }
        }
    }
    return deep[t]==0?false:true;
}
int dfs(int u,int fl){
    if(u==t)
        return fl;
    int x,ans=0;
    for(int i=cur[u];~i;i=e[i].nextt){
        int v=e[i].v;
        if(e[i].w>0&&deep[v]==deep[u]+1){
            x=dfs(v,min(fl-ans,e[i].w));
            ans+=x;
            e[i].w-=x;
            e[i^1].w+=x;
            if(e[i].w)
                cur[u]=1;
            if(ans==fl)
                return fl;
        }
    }
    if(ans==0)
        deep[u]=0;
    return ans;
}
int dinic(){
    int ans=0;
    while(bfs()){
        for(int i=0;i<=t;i++)
            cur[i]=head[i];
        ans+=dfs(s,inf);
    }
    return ans;
}
int check(int x){
//    cout<<x<<"@@"<<endl;
    s=0,t=n+1;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++){
        if(gold[i])
            addedge(s,i,gold[i]);
        if(store[i])
            addedge(i,t,store[i]);
    }
    for(int i=0;i<m;i++){
        if(g[i].w<=x){
            addedge(g[i].u,g[i].v,inf);
            addedge(g[i].v,g[i].u,inf);
        }
    }

//    cout<<p<<"!!"<<endl;
    return dinic();
}
void solve(int r){
    int l=0,ans=-1;
    while(l<=r){
        int midd=(l+r)>>1;
    //    cout<<gsum<<"^^"<<midd<<endl;
        if(check(a[midd])==gsum){
            
            ans=a[midd],r=midd-1;
        }
        else
            l=midd+1;
    }
    if(ans==-1)
        puts("No Solution");
    else
        printf("%d\n",ans);
}
int main(){

    while(~scanf("%d",&n)){
        if(n==0)
            break;
        tot=gsum=ssum=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&gold[i]),gsum+=gold[i];
        for(int i=1;i<=n;i++)
            scanf("%d",&store[i]),ssum+=store[i];
    //    cout<<gsum<<"**"<<ssum<<endl;
        
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&g[i].u,&g[i].v,&g[i].w);
        }
        if(ssum<gsum){
            puts("No Solution");
            continue;
        }
        sort(g,g+m);
        /*for(int i=0;i<m;i++)
            cout<<g[i].w<<endl;*/
        int j=0;
        for(int i=0;i<m;i++){
            if(i==0||g[i].w!=g[i-1].w)
                a[j++]=g[i].w;
        }
    /*    for(int i=0;i<j;i++)
            cout<<a[i]<<endl;*/
    //    cout<<"!!"<<endl;
        solve(j);
    }
    return 0;
}
View Code

 

posted @ 2019-08-06 21:20  starve_to_death  阅读(158)  评论(0编辑  收藏  举报