http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=9
题目大意:
给定一个有向图,每条边都有一个权值,每次你可以选择一个结点v和整数d,把所有以v为终点的边权值减少d,把所有以v为起点的边权值增加d,最后要让所有的边权值非负且最大。
思路:
为了做这题前面先做了好几题差分约束的。。。。
作者思路很巧妙:
不同的操作互不影响,因此可以按任意的顺序实施这些操作,另外,对于同一个结点的多次操作也可以合并,因此令sum(u)为作用于结点u上的所有d之和。这样,本题的目标就是确定所有的sum(u),使得操作之后所有边权值的最小值尽量大。
“最小值最大”使用二分答案的方法。二分答案x之后,问题转化为是否可以让操作完毕后每条边的权值均不小于x。对于边a->b,不难发现操作完毕后它的权值为w(a,b)+sum(a)-sum(b),因此每条边a->b都可以列出一个不等式w(a,b)+sum(a)-sum(b)>=x,移项得sum(b)-sum(a)<=w(a,b)-x。这样,我们实际得到一个差分约束系统。
查分约束系统是指一个不等式组,每个不等式形如xj-xi<=bk,这里的bk是一些事先已知的常数。这个不等式类似于最短路中的不等式d[v]<=d[u]+w(u,v),我们可以用最短路算法求解:对于约束条件xj-xi《=bk,新建一条边i-->j,权值为bk。如果图中有负权环,则差分约束系统无解。
PS:用超级源点的话2.7S,如果不用超级源点,把所有点加入队列那么2.1S。。。以后我还是不用超级源点吧。
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int MAXN=500+10; const int MAXM=2700+10; const int INF=100000+10; int head[MAXN],len,dis[MAXN],vis[MAXN],cnt[MAXN],n,m; struct edge { int to,val,next; }e[MAXM]; void add(int from,int to,int val) { e[len].to=to; e[len].val=val; e[len].next=head[from]; head[from]=len++; } bool spfa() { memset(vis, 0, sizeof(vis)); memset(cnt, 0, sizeof(cnt)); queue<int> q; for(int i = 0; i <=n; i++) { dis[i] = 0;q.push(i); } while(!q.empty()) { int cur=q.front(); q.pop(); vis[cur]=false; for(int i=head[cur];i!=-1;i=e[i].next) { int id=e[i].to; if(dis[id] > dis[cur]+e[i].val) { dis[id]=dis[cur]+e[i].val; if(!vis[id]) { if(++cnt[id] >n) return false; vis[id]=true; q.push(id); } } } } return true; } bool change(int x) { for(int k=1;k<=n;k++) for(int i=head[k];i!=-1;i=e[i].next) e[i].val-=x; bool ok=spfa(); for(int k=1;k<=n;k++) for(int i=head[k];i!=-1;i=e[i].next) e[i].val+=x; return ok; } int main() { while(~scanf("%d%d",&n,&m)) { memset(head,-1,sizeof(head)); len=0; // for(int i=1;i<=n;i++) // add(0,i,0); int L=1,R=0; for(int i=0;i<m;i++) { int from,to,val; scanf("%d%d%d",&from,&to,&val); add(from,to,val); if(val > R) R=val; } if(change(R)) { puts("Infinite"); continue; } else if(!change(L)) { puts("No Solution"); continue; } int ans=L++; while(L<R) { int mid=L+((R-L)>>1); if(!change(mid)) R=mid; else { L=mid+1; ans=mid; } } printf("%d\n",ans); } return 0; }
超级源点。。。以后差分约束不用了- -
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int MAXN=500+10; const int MAXM=27000; const int INF=100000+10; int head[MAXN],len,dis[MAXN],vis[MAXN],cnt[MAXN],n,m; struct edge { int to,val,next; }e[MAXM]; void add(int from,int to,int val) { e[len].to=to; e[len].val=val; e[len].next=head[from]; head[from]=len++; } bool spfa() { for(int i=1;i<=n;i++) { vis[i]=false; dis[i]=INF; cnt[i]=0; } queue<int> q; q.push(0); cnt[0]++; dis[0]=0; vis[0]=true; while(!q.empty()) { int cur=q.front(); q.pop(); vis[cur]=false; for(int i=head[cur];i!=-1;i=e[i].next) { int id=e[i].to; if(dis[id] > dis[cur]+e[i].val) { dis[id]=dis[cur]+e[i].val; if(!vis[id]) { if(++cnt[id] >n) return false; vis[id]=true; q.push(id); } } } } return true; } bool change(int x) { for(int k=1;k<=n;k++) for(int i=head[k];i!=-1;i=e[i].next) e[i].val-=x; bool ok=spfa(); for(int k=1;k<=n;k++) for(int i=head[k];i!=-1;i=e[i].next) e[i].val+=x; return ok; } int main() { while(~scanf("%d%d",&n,&m)) { memset(head,-1,sizeof(head)); len=0; for(int i=1;i<=n;i++) add(0,i,0); int L=1,R=0; for(int i=0;i<m;i++) { int from,to,val; scanf("%d%d%d",&from,&to,&val); add(from,to,val); if(val > R) R=val; } if(change(R)) { puts("Infinite"); continue; } else if(!change(L)) { puts("No Solution"); continue; } int ans=L++; while(L<R) { int mid=L+((R-L)>>1); if(!change(mid)) R=mid; else { L=mid+1; ans=mid; } } printf("%d\n",ans); } return 0; }