/* 参考博文:http://www.cnblogs.com/ylfdrib/archive/2010/09/01/1814478.html 以下题解为转载代码自己写的: zoj2676 胡伯涛论文《最小割模型在信息学竞赛中的应用》中详细介绍了分数规划思想的应用。经典的有最优比率生成树。 对于分数规划的应用中,常用的就是0-1分数规划,即解向量X = {x1, ……,xi, ……}, 对于∀xi∈{0,1}。 主要求解过程是,首先将原分式优化问题,转换成非分式优化问题,利用单调的性质,用二分逼近的方法找到最优解。 题目要求最后能够截得信息,即求某个割,使得c/k最小。 这一题可以将问题转化为最小割,求c/k的最小值,即求sum(xi * ci) / sum(xi * 1)的最小值,(xi == 0 || xi == 1) 设ans = sum(xi * ci) / sum(xi * 1) 则 sum(xi * ci) - ans * sum(xi * 1) = 0 即 sum(xi * (ci - ans)) = 0; 令F(x) = sum(xi * (ci - ans)); 对于一定值ans,函数为单调递减的。 对于正解ANS, F(x) = 0; 则可以推出: 如果 F(x) = 0 那么 ans = ANS 如果 F(x) < 0 那么 ans > ANS 如果 F(x) > 0 那么 ans < ANS 用二分的方法逼近答案,令COST = ci - ans,作为第i条边的新花费,求得最小割ans进行验证即可。 */ #include<stdio.h> #include<string.h> #include<stdlib.h> #include<queue> #include<math.h> using namespace std; #define eps 1e-6//二分1e-10超时 #define inf 0x3fffffff #define N 410 struct node { int u,v,next; double w; }f[N],bian[N*4]; int yong,head[N],cur[N],q[N],gap[N],dis[N]; void init() { yong=0; memset(head,-1,sizeof(head)); } void addedge(int u,int v,double w) { bian[yong].u=u; bian[yong].v=v; bian[yong].w=w; bian[yong].next=head[u]; head[u]=yong++; } void bfs(int start,int endl)//建立到汇点的距离层次图存在dis[]数组中 { int rear=0,i,j; memset(dis,-1,sizeof(dis)); memset(gap,0,sizeof(gap));//gap[x]记录dis[i]=x出现了多少次 dis[endl]=0; gap[dis[endl]]=1; q[rear++]=endl; for(i=0;i<rear;i++) { for(j=head[q[i]];j!=-1;j=bian[j].next) { int v=bian[j].v; if(dis[v]==-1) { ++gap[dis[v]=dis[q[i]]+1]; q[rear++]=v; } } } } double SAP(int start,int endl,int n) { double ans=0; bfs(start,endl); int cur[N];//代替head数组 memcpy(cur,head,sizeof(head)); int stack[N],top=0;//建立手工栈 int u=start,i; while(dis[start]<n) { if(u==endl)//当搜到终点时即找到从原点到汇点的增光路,正常处理即可 { double mini=inf; int tep; for(i=0;i<top;i++) { if(mini>bian[stack[i]].w) { mini=bian[stack[i]].w; tep=i; } } for(i=0;i<top;i++) { bian[stack[i]].w-=mini; bian[stack[i]^1].w+=mini; } ans+=mini; top=tep; u=bian[stack[top]].u;//此时的u为变容量为0的u } if(dis[u]&&gap[dis[u]-1]==0)//出现了断层,没有增广路 break; for(i=cur[u];i!=-1;i=bian[i].next)//遍历与u相连的未遍历的节点 { int v=bian[i].v; if(dis[v]!=-1) { if(bian[i].w>eps&&dis[u]==dis[v]+1)//层次关系找到允许路径 break; } } if(i!=-1)//找到允许弧 { cur[u]=i; stack[top++]=i; u=bian[i].v; } else//无允许的路径,修改标号 当前点的标号比与之相连的点中最小的多1 { int mini=n; for(i=head[u];i!=-1;i=bian[i].next) { if(fabs(bian[i].w)<eps)continue; int v=bian[i].v; if(mini>dis[v])//找到与u相连的v中dep[v]最小的点 { mini=dis[v]; cur[u]=i;//最小标号就是最新的允许弧 } } --gap[dis[u]];//dep[u] 的个数变化了 所以修改gap ++gap[dis[u]=mini+1];//将dep[u]设为min(dep[v]) + 1, 同时修改相应的gap[] if(u!=start)//该点非源点&&以u开始的允许弧不存在,退点 u=bian[stack[--top]].u; } } return ans; } int a[N],len,vis[N]; void dfss(int u) { int i; // printf("%d ",u); for(i=head[u];i!=-1;i=bian[i].next) { int v=bian[i].v; if(bian[i].w>eps&&!vis[v]) { vis[v]=1; dfss(v); } } } int main() { int n,m,i; double st,en,mid,flow; while(scanf("%d%d",&n,&m)!=EOF) { for(i=1; i<=m; i++) scanf("%d%d%lf",&f[i].u,&f[i].v,&f[i].w); st=0; en=10000001; while(st<en+eps) { mid=(st+en)/2; init(); flow=0; for(i=1; i<=m; i++) { // printf("%.2f %.2f ",f[i].w,mid); if(f[i].w<mid+eps) flow=flow+f[i].w-mid; else { addedge(f[i].u,f[i].v,f[i].w-mid); addedge(f[i].v,f[i].u,f[i].w-mid); } } double k=SAP(1,n,n); flow+=k; // printf("%.2f ",k); if(flow>eps) st=mid+eps; else en=mid-eps; } //printf("mid=%.10f ",mid); memset(vis,0,sizeof(vis)); vis[1]=1; dfss(1); len=0; for(i=1;i<=m;i++) { // printf("%.10f %.10f ",f[i].w,mid+eps);//&&vis[f[i].u]+vis[f[i].v]==1 if(vis[f[i].u]+vis[f[i].v]==1||f[i].w<eps+mid)//如果是负边或者割边 len++; } printf("%d ",len); int ok=0; for(i=1;i<=m;i++) if(vis[f[i].u]+vis[f[i].v]==1||f[i].w<mid+eps) {//如果是负边或者割边 if(ok) printf(" "); printf("%d",i); ok=1; } printf(" "); } return 0; }