题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1676
题意描述:07年集训队胡伯涛论文题,求边割的权值和除以边的条数的值最小(注意这个割不是我们平常所说的割,它除了包含我们平常所说的割,还可能包含其他的边,这样有可能使得所求的平均值更小);
解决:0-1分数规划的问题可以转化为最小割模型来解决,题目解决的问题就是minize x=,则设g(x)=(x即为r)因为ci只能取0或1所以能转为最小割模型模型,ci为组成一个 向量,wi组成一个行向量;
定理1:当且仅当g(x)=0时x的能达到最小,所以可以枚举x的值从而使g(x)为0则得到解
定理2:函数g(x)为单调递减的函数,所以在枚举x的时候:
g(x)>0 则将x的值减小
g(x)<0则将x的值增大
g(x)=0则得到结果
那么现在的任务显然是求g(x)然后判断与0的关系,那么如何求g(x)呢?将原来图中边的权值都改为w-x*c,因为是0-1归回,c只能去0或者1,那么就可以将权值改为w-x,这样后如果w-x为负则说明该边一定在割内,为什么呢?因为该边加上去之后会使求得的割更小,之后为正的建图后求最小割即可,还有个恶心的问题需要注意精度!
贴代码先:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 150;
const int M = 2000;
const double eps = 1e-5;
const double Max = 0xfffffff;
struct node
{
int u,v, next;
double w;
}edge1[M],edge[M];
int first1[N],first[N],e1,e;
int n,vis[N],flag[M],level[N];
int Que[N];
int S,T;
int dist[N],now[N],cnt[N],pre[N];
double cur[N];
void add1(int u, int v ,double w)
{
edge1[e1].v=v;
edge1[e1].w = w;
edge1[e1].next =first1[u];
first1[u]=e1++;
}
void add(int u, int v, double w)
{
edge[e].u=u;
edge[e].v = v;
edge[e].w = w;
edge[e].next = first[u];
first[u] = e++;
edge[e].u=v;
edge[e].v = u;
edge[e].w = 0;
edge[e].next=first[v];
first[v]=e++;
}
void dfs(int u)
{
vis[u]=1;
for(int i = first[u];i!=-1;i=edge[i].next)
{
int v = edge[i].v;
if(vis[v]==0&&edge[i].w>eps)
dfs(v);
}
}
double SAP()
{
int i,j,k;double flow=Max,tot=0,min;
bool found;
memset(pre,-1,sizeof(pre));
memset(cnt,0,sizeof(cnt));
memset(now,-1,sizeof(now));
memset(dist,0,sizeof(dist));
i=S;
cnt[0]=T+1;
while(dist[S]<=T)
{
cur[i]=flow;
found=false;
if(now[i]==-1)
k=first[i];
else
k=now[i];
for(;k!=-1;k=edge[k].next)
{
j=edge[k].v;
if(edge[k].w>eps&&dist[j]+1==dist[i])
{
found=true;
pre[j]=k;
now[i]=k;
i=j;
if(edge[k].w<flow)
flow=edge[k].w;
if(i==T)
{
tot+=flow;
while(i!=S)
{
edge[pre[i]].w-=flow;//注意是pre[i]
edge[pre[i]^1].w+=flow;
i=edge[pre[i]].u;//e[pre[i]].u
}
flow=Max;
}
break;
}
}
if(found) continue;
if(--cnt[dist[i]]==0) break;
for(min=T,k=first[i];k!=-1;k=edge[k].next)
{
j=edge[k].v;
if(edge[k].w>eps&&min>dist[j])
{
now[i]=k;
min=dist[j];
}
}
dist[i]=min+1;
cnt[dist[i]]++;
if(i!=S)
{
i=edge[pre[i]].u;
flow=cur[i];
}
}
return tot;
}
int main ()
{
int u, v, m,i,num;
double w,max,right,left,sum,maxflow,mid;
while(scanf("%d%d",&n,&m)!=EOF)
{
e1 = 0;
memset(first1,-1,sizeof(first1));
max=0;
for(i = 0;i<m;i++)
{
scanf("%d%d%lf",&u,&v,&w);
if(max < w)
max=w;
add1(u,v,w);
add1(v,u,w);
}
left=0;right=max;
S=1; T = n;
int ans=0;
while(left<right||fabs(left-right)<eps) //枚举每个结果值然后判断是否为最优
{
ans++;
mid = (left+right)/2.0;
e=0;
sum =0;
memset(first,-1,sizeof(first));
memset(flag,0,sizeof(flag));
for(u = 1;u<=n;u++)
for(i = first1[u]; i!=-1;i=edge1[i].next)
if(edge1[i].w-mid<0&&!flag[i^1]) //若权值为负那么一定取,因为是邻接表建图一条无向边被当成两条有向边建,而sum只能算一次,所以需要flag来标记一下
{
sum+=edge1[i].w-mid;
flag[i]=1;flag[i^1]=1;
}
else if(edge1[i].w-mid>0)//因为取零的时候对结果无影响所以建图的时候可以省略
add(u,edge1[i].v,edge1[i].w-mid);
maxflow = SAP();
sum+=maxflow;
if(fabs(sum)< eps)
break;
else if(sum < 0)
right=mid-eps;
else left=mid+eps;
}
memset(vis,0,sizeof(vis));
dfs(1);
for(i=1;i<=n;i++)
for(e = first1[i];e!=-1;e=edge1[e].next)
if(vis[i]&&!vis[edge1[e].v])
flag[e]=1;
num=0;
for(i=0;i<e1;i+=2)
if(flag[i]||flag[i+1])
num++;
printf("%d\n",num);
for(i=0;i<e1;i+=2)
if(flag[i]||flag[i+1])
printf("%d ",i/2+1);
printf("\n");
}
return 0;
}
总结与感想:这题写了好久,都是精度惹的祸,不会处理,个人理解精度问题就是将0周围一定范围的数都当成0对待,该题参照了yw大神的代码,在此表示感谢,开始贴了个dinic模板求最小割居然tle了,也没明白为什么会tle,最后果断换成ISAP才过