最小修改边权

ž给定一个带权图和图中的一棵生成树
ž可以修改边权
—目标:  使得给定的生成树是该图的MST
—要求修改边权的总量最小
可以增加、可以减少

一开始以为是最小割,可是树边与非树边可以同时改变,令人很不爽。

结果发现是htx的noip模拟题,是用km算法解决的。。。

于是又碰到了一个km算法非最优匹配应用。

设w[i]为树边,w[j]为非树边,我们只会减树边、加非树边,所以可以写出不等式(w[i]-l[i])<=(w[j]+r[j]),移项得w[i]-w[j]<=l[i]+r[j],这就很像km算法中的顶标不等式,而km算法正好能使l[i]+r[j]最小,那么问题解决。

这类问题的重要特点便是w[i]-w[j]<=l[i]+r[j],并且最小化右式,一开始我想直接用边权做顶标,套用不等式,可是没办法解决最小化问题,所以还是要抓住问题关键。

#include <cstdio>
#include <cstdlib>
#include <cstring>
int tail[50000],tail2[50000],next[50000],cc[50000],next2[50000],cost2[50000],pow[50000],sora[50000],sora2[50000],p[50000],q[50000],st[50000],lx[50000],ly[50000],vx[50000],vy[50000],d[50000],b[50000],slack[50000];
int ss,ss2,n,m;
const int oo=1073741819;
void origin(){for (int i=1;i<=n+m-1;i++) tail[i]=i,tail2[i]=i;ss=n+m-1,ss2=n+m-1;}
void link(int x,int y,int i){ss++,next[tail[x]]=ss,tail[x]=ss,sora[ss]=y,pow[ss]=i;}
void link2(int x,int y,int z){ss2++,next2[tail2[x]]=ss2,tail2[x]=ss2,sora2[ss2]=y,cost2[ss2]=z;if (z>lx[x]) lx[x]=z;}
int min(int x,int y) {return (x<y) ? x : y;}
void bfs(int x,int y,int k)
{
  int h,r,i,ne,na;
  memset(d,127,sizeof(d));
  h=0,st[r=1]=x,d[x]=0,p[x]=0;
  for (;h<r;)
  {
    ne=st[++h];
    for (i=ne;next[i]!=0;)
    {
      i=next[i],na=sora[i];
      if (d[ne]+1<d[na]) d[na]=d[ne]+1,p[na]=ne,q[na]=pow[i],st[++r]=na;
    }
  }
  for (i=y;p[i];i=p[i]) link2(q[i],k,cc[q[i]]-cc[k]);
}
int find(int x)
{
  if (vx[x]) return 0;
  vx[x]=1;
  for (int i=x,tmp,ne;next2[i]!=0;)
  {
    i=next2[i],ne=sora2[i];
    tmp=lx[x]+ly[ne]-cost2[i];
    if ((!vy[ne])&&(0==tmp))
    {
      vy[ne]=1;
      if ((0==b[ne])||(find(b[ne])))
      {
        b[ne]=x;return 1;
      }
    }
    else if (tmp<slack[ne]) slack[ne]=tmp;
  }
  return 0;
}
void km()
{
  memset(b,0,sizeof(b));
  for (int i=1,j,d;i<=n-1;i++)
  {
    memset(vx,0,sizeof(vx)),memset(vy,0,sizeof(vy)),memset(slack,127,sizeof(slack));
    if (35==i)
      d--;
    for (;!find(i);)
    {
      d=oo;
      for (j=n;j<=m;j++) if (!vy[j]) d=min(d,slack[j]);
      for (j=n;j<=m;j++) if (vy[j]) ly[j]+=d;else slack[j]-=d;
      for (j=1;j<=n-1;j++) if (vx[j]) lx[j]-=d;
      memset(vx,0,sizeof(vx)),memset(vy,0,sizeof(vy));
    }
  }
}
void init()
{
  int i,x,y,z;
  scanf("%d%d\n",&n,&m);
  origin();
  for (i=1;i<=m;i++)
  { scanf("%d%d%d\n",&x,&y,&z);cc[i]=z;
    if (i<=n-1) link(x,y,i),link(y,x,i);else bfs(x,y,i),ly[i]=0;
  }
  for (i=m+1;i<=m+n-1;i++)
    for (int j=1;j<=n-1;j++) link2(j,i,0);
  m=m+n-1;
  km();
  for (i=1;i<=n-1;i++) printf("%d\n",cc[i]-lx[i]);
  for (i=n;i<=m-n+1;i++) printf("%d\n",cc[i]+ly[i]);
}
int main()
{
  freopen("ti.in","r",stdin);
  freopen("ti.out","w",stdout);
    init();
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值