[单纯形 || 差分费用流 || 辅助变量费用流] BZOJ 3112 [Zjoi2013]防守战线

UPD:好吧 我绝望的发现这些乱七八糟的本质上全是一样的 此文已废 2017.06.07


这个题目啊


我们用样例说话吧 列出来的式子是这样的


对偶一下


By the way 这个的解 是 3 1 2 

看到这个东西直接无脑simplex啊 管他是不是全幺模

然后就过了


#include<cstdio>
#include<cstdlib>
#include<cmath>
#define eps 1e-10
#define inf 1e20;
using namespace std;

inline int dcmp(double a,double b){
  if (fabs(a-b)<eps) return 0;
  if (a<b) return -1;
  return 1;
}

int n,m;
double Ans;
double a[1005][10005];
int next[10005];

inline void PIVOT(int l,int e){
  for (int i=0;i<=n;i++)
    if (i!=e)
      a[l][i]/=a[l][e];
  a[l][e]=1/a[l][e];
  int last=-1;
  for (int i=0;i<=n;i++)
    if (a[l][i])
      next[i]=last,last=i;
  for (int i=0;i<=m;i++){
      if (i==l || a[i][e]==0) continue;
      for (int j=last;j!=-1;j=next[j]){
	if (j==e) continue;
	a[i][j]-=a[i][e]*a[l][j]; 
      }
      a[i][e]=-a[i][e]*a[l][e];
  }
}

inline double Simplex(){
  int l,e;
  double minimum;
  while (1){
    for (e=1;e<=n && a[0][e]<=0;e++);
    if (e==n+1) return -a[0][0];
    minimum=inf;
    for (int i=1;i<=m;i++)
      if (a[i][e]>0 && dcmp(minimum,a[i][0]/a[i][e])>0)
	minimum=a[l=i][0]/a[i][e];
    PIVOT(l,e);
  }
}

int main(){
  int l,r,w;
  freopen("defend.in","r",stdin);
  freopen("defend.out","w",stdout);
  scanf("%d%d",&m,&n);
  for (int i=1;i<=m;i++) scanf("%lf",&a[i][0]);
  for (int i=1;i<=n;i++){
    scanf("%d%d%lf",&l,&r,&a[0][i]);
    for (int j=l;j<=r;j++) a[j][i]=1;
  }
  Ans=Simplex();
  printf("%.0lf\n",Ans);
  return 0;
}



可是考场上simplex板子没背熟怎么办啊

我们可以上费用流!


差分费用流是什么呢

我们看这个


红色表示容量 绿色表示费用

每个不等式弄成一个点

红色出边代表式子的限制

绿色的边流一个流量会发现就是一个变量++ 对一串不等式有贡献

这就是个最大费用可行流啊

然后激动的建完图后发现不会搞有正环的东西啊

我们这么搞 建个超级源汇 把正边强制流满 对原图流量不平衡了

然后在新图上跑最小费用最大流 这就是把超级源汇多出来的不平衡流量全部退流  同时是费用降低的最少

建图是不是很棒 然后这个题费用流狗带了 zkw都救不了 只有70分


UPD 17/02/14

这个建图可以直接由最初的LP对偶而来 

具体我另开了一篇文:http://blog.csdn.net/u014609452/article/details/55101021


#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}

inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=100005;

struct edge{
  int u,v,f,w;
  int next;
}G[N<<1];
int head[N],inum=1;

inline void add(int u,int v,int w,int f,int p){
  G[p].u=u; G[p].v=v; G[p].w=w; G[p].f=f; G[p].next=head[u]; head[u]=p;
}
inline void link(int u,int v,int w,int f){
  add(u,v,w,f,++inum); add(v,u,-w,0,++inum);
}

int S,T;
int pre[N],dis[N],ins[N];
int Q[1000005],l,r;

#define V G[p].v

ll Mincost;
int icnt=0;
inline bool SPFA(){
  for (int i=1;i<=T;i++) dis[i]=1<<30,pre[i]=0,ins[i]=0;
  l=r=-1; dis[S]=0; Q[++r]=S; ins[S]=1;
  while (l!=r){
    int u=Q[++l]; ins[u]=0;
    for (int p=head[u];p;p=G[p].next)
      if (G[p].f && dis[V]>dis[u]+G[p].w){
	dis[V]=dis[u]+G[p].w; pre[V]=p;
	if (!ins[V]) Q[++r]=V,ins[V]=1;
      }
  }
  if (dis[T]==(1<<30)) return 0;
  int minv=1<<30;
  for (int p=pre[T];p;p=pre[G[p].u])
    minv=min(minv,G[p].f);
    Mincost+=(ll)minv*dis[T];
  for (int p=pre[T];p;p=pre[G[p].u])
    G[p].f-=minv,G[p^1].f+=minv;
  return 1;
}

int n,m; ll Ans;
int deg[N];

int main(){
  int iu,iv,iw,maxw=0;
  freopen("defend.in","r",stdin);
  freopen("defend.out","w",stdout);
  read(n); read(m); S=n+1+1; T=n+1+2;
  for (int i=1;i<=n;i++)
    read(iw),link(i,i+1,0,iw),maxw=max(maxw,iw);
  link(n+1,1,0,1<<30); 
  for (int i=1;i<=m;i++){
    read(iu); read(iv); read(iw);
    link(iu,iv+1,iw,maxw); deg[iu]+=maxw,deg[iv+1]-=maxw;
    Ans+=(ll)maxw*iw;
  }
  for (int i=1;i<=n+1;i++)
    if (deg[i]>0)
      link(S,i,0,deg[i]);
    else if (deg[i]<0)
      link(i,T,0,-deg[i]);
  while (SPFA());
  printf("%lld\n",Ans-Mincost);
  return 0;
}



换一种建图方式 辅助变量是什么呢

我们添加辅助变量把不等式变为等式


一通差分


仔细观查 可以得到每个变量在其中出现一正一负 然后可以联想到流量平衡 

将每个不等式看做一个点 每个变量看成一条边 等式右边的值是超级源汇提供这个点的流量

就是每个变量从为正的点连为负的点 有费用 

超级源向右边为正的点连边 为容量

超级汇向右边为负的点连边 绝对值为容量

具体建图我画不出来 这个idea是看BYVOID神犇关于employee的题解:https://www.byvoid.com/blog/noi-2008-employee/#more-916


这个费用流跑的还是不行 拷了个zkw模板就过了

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}

inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=100005;

struct edge{
  int u,v,f,w;
  int next;
}G[N<<1];
int head[N],inum=1;

inline void add(int u,int v,int w,int f,int p){
  G[p].u=u; G[p].v=v; G[p].w=w; G[p].f=f; G[p].next=head[u]; head[u]=p;
}
inline void link(int u,int v,int w,int f){
  add(u,v,w,f,++inum); add(v,u,-w,0,++inum);
}

int S,T;
int pre[N],dis[N],ins[N];
int Q[1000005],l,r;

#define V G[p].v

ll Maxcost;
int icnt=0;
inline bool SPFA(){
  for (int i=1;i<=T;i++) dis[i]=-1<<30,pre[i]=0,ins[i]=0;
  l=r=-1; dis[S]=0; Q[++r]=S; ins[S]=1;
  while (l!=r){
    int u=Q[++l]; ins[u]=0;
    for (int p=head[u];p;p=G[p].next)
      if (G[p].f && dis[V]<dis[u]+G[p].w){
	dis[V]=dis[u]+G[p].w; pre[V]=p;
	if (!ins[V]) Q[++r]=V,ins[V]=1;
      }
  }
  if (dis[T]==(-1<<30)) return 0;
  int minv=1<<30;
  for (int p=pre[T];p;p=pre[G[p].u])
    minv=min(minv,G[p].f);
  Maxcost+=(ll)minv*dis[T];
  for (int p=pre[T];p;p=pre[G[p].u])
    G[p].f-=minv,G[p^1].f+=minv;
  return 1;
}

int n,m;;
int val[N];

int main(){
  int iu,iv,iw,maxw=0;
  freopen("defend.in","r",stdin);
  freopen("defend.out","w",stdout);
  read(n); read(m); S=n+1+1; T=n+1+2;
  for (int i=1;i<=n;i++) read(val[i]);
  for (int i=1;i<=m;i++){
    read(iu); read(iv); read(iw);
    link(iu,iv+1,iw,1<<30);
  }
  for (int i=1;i<=n;i++)
    link(i,i+1,0,1<<30);
  for (int i=1;i<=n+1;i++)
    if (val[i]-val[i-1]>0)
      link(S,i,0,val[i]-val[i-1]);
    else
      link(i,T,0,val[i-1]-val[i]);
  while (SPFA());
  printf("%lld\n",Maxcost);
  return 0;
}

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}

inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
#define MAXN 55555  
#define MAXM 55555  
#define INF 100000007  
using namespace std;  
struct EDGE{  
  int cost, cap, v;  
  int next, re;  
}edge[MAXM];  
int head[MAXN], e;  
int vis[MAXN], d[MAXN];  
ll ans, cost;
int src, des, n;  
void init(){  
  memset(head, -1, sizeof(head));  
  e = 0;  
  ans = cost = 0;  
}  
void add(int u, int v, int cap, int cost)  {  
  edge[e].v = v;  edge[e].cap = cap;  edge[e].cost = cost;  edge[e].re = e + 1;  edge[e].next = head[u];  head[u] = e++;  
  edge[e].v = u;  edge[e].cap = 0;  edge[e].cost = -cost;  edge[e].re = e - 1;  edge[e].next = head[v];  head[v] = e++;  
}  
int aug(int u, int f){  
  if(u == des){  
    ans += (ll)cost * f;  
    return f;  
  }  
  vis[u] = 1;  
  int tmp = f;  
  for(int i = head[u]; i != -1; i = edge[i].next)  
    if(edge[i].cap && !edge[i].cost && !vis[edge[i].v]){  
      int delta = aug(edge[i].v, tmp < edge[i].cap ? tmp : edge[i].cap);  
      edge[i].cap -= delta;  
      edge[edge[i].re].cap += delta;  
      tmp -= delta;  
      if(!tmp) return f;  
    }  
  return f - tmp;  
}  
bool modlabel(){  
  for(int i = 0; i <= n; i++) d[i] = INF;  
  d[des] = 0;  
  deque<int>Q;  
  Q.push_back(des);  
  while(!Q.empty()){  
    int u = Q.front(), tmp;  
    Q.pop_front();  
    for(int i = head[u]; i != -1; i = edge[i].next)  
      if(edge[edge[i].re].cap && (tmp = d[u] - edge[i].cost) < d[edge[i].v])  
	(d[edge[i].v] = tmp) <= d[Q.empty() ? src : Q.front()] ? Q.push_front(edge[i].v) : Q.push_back(edge[i].v);  
  }  
  for(int u = 1; u <= n; u++)  
    for(int i = head[u]; i != -1; i = edge[i].next)  
      edge[i].cost += d[edge[i].v] - d[u];  
  cost += d[src];  
  return d[src] < INF;  
}  
void costflow(){  
  while(modlabel()){  
    do{  
      memset(vis, 0, sizeof(vis));  
    }while(aug(src, INF));  
  }  
}  
int in,im;;
int val[100005];

int main(){
  int iu,iv,iw,maxw=0;
  freopen("defend.in","r",stdin);
  freopen("defend.out","w",stdout);
  read(in); read(im); src=in+1+1; des=in+1+2; n=des;
  init();
  for (int i=1;i<=in;i++) read(val[i]);
  for (int i=1;i<=im;i++){
    read(iu); read(iv); read(iw);
    add(iu,iv+1,1<<30,-iw);
  }
  for (int i=1;i<=in;i++)
    add(i,i+1,1<<30,0);
  for (int i=1;i<=in+1;i++)
    if (val[i]-val[i-1]>0)
      add(src,i,val[i]-val[i-1],0);
    else
      add(i,des,val[i-1]-val[i],0);
  costflow();
  printf("%lld\n",-ans);
  return 0;
}


这里还有个讨论帖 也安利下吧:http://tieba.baidu.com/p/2242087161?pid=30925559295&see_lz=1


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值