GDKOI2016 Day1 T3 寻宝

来源:互联网 发布:方维互动直播系统源码 编辑:程序博客网 时间:2024/05/18 09:09

T3 寻宝

给出N个点,点与点之间有依赖关系,形如选了这个点,必须要选哪些点。每个点有两个权,a和b,求选出一些合法的点集,使得biai最小化。若无解则输出一个奇怪的字符串。

01分数规划。如果答案是l,则满足biai=l移项得bilai=0。发现最小值很难看,于是聪明滴把两边同时乘一个-1,得laibi=0取最大值,然后有依赖关系就可以用最大权闭合子图做了。
二分一个mid把每个点的点权变成midab,然后对原图找一个最大权闭合子图,判断是否大于0即可。这个东西可以用最大流(最小割)来解决,对于每一个依赖关系连一条容量为+∞的边,从S向每一个正权点连容量为其权值的边,每一个负权点向T连一条容量为其权值的相反数的边,然后最终答案就是所有正权点的权值和-最小割。
证明请感性理解。(似乎所有网络流的证明都得感性理解)
注意判无解,感觉我的方法有点水。

#include<cstdio>#include<cstring>#include<algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)#define rep(i,a) for(int i=last[a];i;i=next[i])#define inf 0x7fffffff#define db double#define N 1005#define M 55005using namespace std;int n,x,y,S,T,l,ans,dis[N],d[N],a[N][N],b[N],c[N];int t[M],next[M],last[N];db f[M];void add(int x,int y,db z) {    t[++l]=y;f[l]=z;next[l]=last[x];last[x]=l;    t[++l]=x;f[l]=0;next[l]=last[y];last[y]=l;}bool bfs() {    memset(dis,0,sizeof(dis));dis[S]=1;d[1]=S;    int i=0,j=1;    while (i<j)         rep(k,d[++i]) if (f[k]&&!dis[t[k]]) {            dis[t[k]]=dis[d[i]]+1;d[++j]=t[k];          }    return dis[T];}db dinic(int x,db y) {    if (x==T) return y;    db now=0;    rep(i,x) if (f[i]&&dis[t[i]]==dis[x]+1) {        db k=dinic(t[i],min(y,f[i]));        f[i]-=k;f[i^1]+=k;now+=k;y-=k;        if (!y) break;    }    if (!now) dis[x]=-1;return now;}bool check(db x) {    l=1;memset(last,0,sizeof(last));    db ans=0;    fo(i,1,n)        if (x*b[i]-c[i]>0) add(S,i,x*b[i]-c[i]),ans+=x*b[i]-c[i];        else add(i,T,c[i]-x*b[i]);    fo(i,1,n)        fo(j,1,a[i][0]) add(i,a[i][j],inf);    while (bfs()) ans-=dinic(S,inf);    if (ans>0) return 1;else return 0;}int main() {    scanf("%d",&n);S=0;T=n+1;    fo(i,1,n) {        scanf("%d",&a[i][0]);        fo(j,1,a[i][0]) scanf("%d",&a[i][j]);    }    fo(i,1,n) scanf("%d%d",&b[i],&c[i]);    db l=0,r=100000,mid;    while (r-l>1e-6) {        mid=(l+r)*1.0/2;        if (check(mid)) r=mid; else l=mid;    }    if (l>1e-6) printf("%.6lf",l);    else printf("CanNotFindTreasure!");}
1 0
原创粉丝点击