【洛谷3308】[SDOI2014] LIS(网络流)

点此看题面

  • 给定一个长度为(n)的序列(a_i),每个元素有一个删除代价(b_i)和附加属性(c_i)(附加属性互不相同)。
  • 求最小的删除代价使得最长上升子序列长度至少减(1),并求出代价最小的一个方案,使得删除元素的附加属性排序后的字典序尽可能小。
  • 数据组数(le5,nle700)

(LIS)经典建图

众所周知(LIS)有一个经典的建图,设(f_i)表示以(i)为结尾的最长上升子序列长度。

(j)(i)连边,当且仅当(j<i,a_j<a_i,f_j+1=f_i)。特殊地,超级源向所有(f_i=1)的点连边,(f_i=max_{k=1}^n f_k)的点向超级汇连边。

然后这道题删除一个点有一个代价,我们就拆点,在(i)的入点和出点之间连一条容量为(b_i)的边,其余不同位置的点之间的连边容量直接设为(INF)表示不可删去。

然后只要跑一遍网络流求最小割就能得到最小删除代价了。

附加属性字典序最小的方案

显然从小到大枚举(c_k),判断(k)能否被选在最小割集中。

这个判断只需调用一下(BFS)函数判一下是否存在一条从(k)的入点到出点的增广路,有的话则无法选在最小割集中。

而选中了一条边就需要消除它的影响,即退流,这只要从超级汇向出点、从入点向超级源这样跑两次与原本反向的网络流即可。然后把这条边及其反向边的容量都设为(0)彻底抹杀。

代码:(O(Dinic))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 700
#define INF 1e9
using namespace std;
int n,a[N+5],b[N+5],c[N+5],f[N+5],ans[N+5];pair<int,int> p[N+5];
namespace Dinic//网络流
{
	#define PS (2*N+2)
	#define ES (N*N+2*N)
	#define E(x) ((((x)-1)^1)+1)
	#define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
	int ee,lnk[PS+5],cur[PS+5];struct edge {int to,nxt,F;}e[2*ES+5];
	I void Cl() {ee=0;for(RI i=1;i<=2*n+2;++i) lnk[i]=0;} 
	I void Add(CI x,CI y,CI f) {add(x,y,f),add(y,x,0);}
	int q[PS+5],D[PS+5];I bool BFS(CI s,CI t)//BFS求增广路
	{
		RI i,k,H=1,T=1;for(i=1;i<=2*n+2;++i) D[i]=-1;D[q[1]=s]=0;
		W(H<=T&&!~D[t]) for(i=lnk[k=q[H++]];i;i=e[i].nxt)
			e[i].F&&!~D[e[i].to]&&(D[q[++T]=e[i].to]=D[k]+1);return ~D[t];
	}
	I int DFS(CI x,CI t,RI f=INF)//DFS增广
	{
		if(x==t||!f) return f;RI i,o,g=0;for(i=cur[x];i;i=e[i].nxt)
		{
			if(D[e[i].to]^(D[x]+1)||!(o=DFS(e[i].to,t,min(f,e[i].F)))) continue;
			if(g+=o,e[i].F-=o,e[E(i)].F+=o,!(f-=o)) break;
		}return cur[x]=i,g;
	}
	I int MaxFlow(CI s,CI t) {RI f=0;W(BFS(s,t)) memcpy(cur,lnk,sizeof(cur)),f+=DFS(s,t);return f;}//最大流
}
int main()
{
	#define s 2*n+1//超级源
	#define t 2*n+2//超级汇
	RI Tt,i,j,k,ct,Mx;scanf("%d",&Tt);W(Tt--)
	{
		for(scanf("%d",&n),Dinic::Cl(),i=1;i<=n;++i) scanf("%d",a+i);
		for(i=1;i<=n;++i) scanf("%d",b+i),Dinic::Add(i,n+i,b[i]);//入点连向出点
		for(i=1;i<=n;++i) scanf("%d",c+i),p[i]=make_pair(c[i],i);sort(p+1,p+n+1);//按附加属性排序
		for(Mx=0,i=1;i<=n;Mx=max(Mx,f[i++])) for(f[i]=j=1;j^i;++j) a[i]>a[j]&&(f[i]=max(f[i],f[j]+1));//预处理DP
		for(i=1;i<=n;++i)
		{
			f[i]==1&&(Dinic::Add(s,i,INF),0),f[i]==Mx&&(Dinic::Add(n+i,t,INF),0);//与超级源汇的连边
			for(j=1;j^i;++j) a[i]>a[j]&&f[i]==f[j]+1&&(Dinic::Add(n+j,i,INF),0);//分层建图
		}
		for(printf("%d ",Dinic::MaxFlow(s,t)),ct=0,i=1;i<=n;++i)
		{
			if(k=p[i].second,Dinic::BFS(k,n+k)) continue;ans[++ct]=k;//判断有没有增广路
			Dinic::MaxFlow(t,n+k),Dinic::MaxFlow(k,s),Dinic::e[2*k-1].F=Dinic::e[2*k].F=0;//退流,删边
		}
		for(printf("%d
",ct),sort(ans+1,ans+ct+1),i=1;i<=ct;++i) printf("%d%c",ans[i]," 
"[i==ct]);
	}return 0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3308.html