把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

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

点此看题面

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

\(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\n",ct),sort(ans+1,ans+ct+1),i=1;i<=ct;++i) printf("%d%c",ans[i]," \n"[i==ct]);
	}return 0;
}
posted @ 2021-05-10 18:35  TheLostWeak  阅读(37)  评论(0编辑  收藏  举报