思维并查集/网络流和二分
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; }
网络流做法:
分析:对于每个有金矿的点,源点向其连一条容量为金矿数的边,对于每个仓库,向汇点连一条容量为仓库储存量的边,然后对于所有边长不大于当前枚举的值的边,连双向的容量为无穷的边
如果满流,则该枚举的边长可行,继续缩小,否则,枚举更大的边长
#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; }