acm-(好题、网络流、状压dp)Educational Codeforces Round 96 (Rated for Div. 2) G. Yet Another DAG Problem

题面
传送门
本题有多种方法。

方法一:采用状压dp,用 S S S记录点集,我们考虑设 d p [ S ] dp[S] dp[S]为一个合法的 D A G DAG DAG点集 S S S对应的最小答案。然后我们容易发现图中所有点的点权的范围一定可以位于 [ 0 , n − 1 ] [0,n-1] [0,n1],再大一定不会更优。而且由于是一个 D A G DAG DAG,根据题意我们从 D A G DAG DAG的入度为零的点出发沿着边走到终点,这个过程中遇到的点的点权一定是递减的。

现在考虑 d p [ S ] dp[S] dp[S]已经求解出来,那么如何用 d p [ S ] dp[S] dp[S]去更新其它的状态,设 r e s res res S S S的补集,那么我们需要从 r e s res res中选择一个子集 r e s ′ res' res,满足 r e s ′ res' res中的所有点的入边对应的那个端点位于 S S S以内,那么 r e s ′ res' res可以与 S S S合并为一个新集合 S ′ S' S,也是一个dp的新状态,符合它是一个 D A G DAG DAG的条件。然后我们并不是一次性求出每条边的贡献,而是将贡献分配到每一步更新状态中,比如对于 S S S中的点而言,从 S S S S ′ S' S我们会增加从 S S S中的点出发到达 r e s res res中所有点的边权之和(不太好描述,可以根据代码意会),这样做一定是正确的,我们相当于是在分层图之间 d p dp dp,对于层数靠后的点,每靠近它们一层我们加一次它们跟现在已有的点的边权贡献,相当于总贡献就是 层 数 之 差 ∗ 边 权 层数之差*边权 ,也就是说只要还没 d p dp dp到它们就会一直被计算贡献,是符合题目给出的贡献定义的。

那么现在我们还需要预处理 v a l [ S ] val[S] val[S]数组,也就是每次 d p dp dp该加的贡献,考虑 v a l [ S ] val[S] val[S]也可以被递推求解,满足 v a l [ S ] = v a l [ S − l o w b i t ( S ) ] + ∑ 从 S 中 的 点 到 r e s 中 的 点 w − ∑ 从 S 中 的 点 到 l o w b i t ( S ) w val[S]=val[S-lowbit(S)]+\sum_{从S中的点到res中的点} w-\sum_{从S中的点到lowbit(S)}w val[S]=val[Slowbit(S)]+SreswSlowbit(S)w

具体实现代码的时候, i n e [ S ] ine[S] ine[S]代表状态 S S S对应的入边的端点集合。

int w[maxn][maxn],ine[1<<maxn],id[1<<maxn],path[1<<maxn],a[maxn];
ll val[1<<maxn],dp[1<<maxn];
int main(){
	int n=rd(),m=rd();
	FOR(i,1,m+1){
		int u=rd()-1,v=rd()-1;
		w[u][v]=rd();
		ine[1<<v]|=1<<u;
	}
	FOR(i,0,n)id[1<<i]=i;
	FOR(i,1,1<<n){
		int u=id[i&-i];
		ll wvu=0,wuv=0;
		FOR(j,0,n){
			if((1<<j)&i){
				wvu+=w[j][u];
			}else wuv+=w[u][j];
		}
		val[i]=val[i-(i&-i)]+wuv-wvu;
		ine[i]=(ine[i-(i&-i)]|ine[i&-i]);
	}
	FOR(i,1,1<<n)dp[i]=INF;
	FOR(i,0,(1<<n)-1){
		if(dp[i]==INF)continue;
		int res=((1<<n)-1)^i;
		for(register int j=res;j;j=(j-1)&res){
			if((ine[j]&i)==ine[j] && dp[j|i]>dp[i]+val[i]){
				dp[j|i]=dp[i]+val[i];
				path[j|i]=i;
			}
		}
	}
	int cur=(1<<n)-1,now=0;
	while(cur){
		int last=path[cur];
		int d=cur^last;
		FOR(j,0,n)if((1<<j)&d)a[j]=now;
		now++;
		cur=last;
	}
	FOR(i,0,n)printf("%d ",a[i]);
	puts("");
}

方法二:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值