线性规划与对偶问题

首先,讲下什么是线性规划。具有如下形式:

即为一个线性规划问题(它是标准形式,也就是只要能化成这种形式的都是线性规划问题)。

现在我们讲讲什么是对偶问题。

对偶问题的标准形式:

原问题:

对于一个矩阵 \(A\) ,列向量 \(c,b\) ,求:

\[\max c^Tx\\ \\ s.t. \begin{cases} Ax\le b\\ x\ge 0\\ \end{cases} \]

对偶问题:

\[\min b^Ty\\ s.t.\begin{cases} A^Ty\ge c\\ y\ge 0\\ \end{cases} \]

其中列向量 \(x,y\) 是两个变量。

P7246 手势密码

我们考虑将问题描述为线性规划,设集族 \(\mathcal I\) 包含了所有的路径,那么我们无非要给每条路径 \(S\) 赋予一个权值 \(x_S\) 表示这条路径走了多少次,容易写出约束:

\[\forall u, \sum_{S\ni u} x_S=a_u\\ \]

我们的目的就是求:

\[\min\sum_{S\in \mathcal I} x_S \]

线性规划一般是用不等式表述的,所以我们先将前面的等式拆成两组不等式,就可以写成标准的形式:

\[s.t.\begin{cases} \sum_{S\ni u}\limits x_S\ge a_u&(s_u)\\ \sum_{S\ni u}\limits - x_S\le -a_u&(t_u)\\ x_S\ge 0 \end{cases}\\ \]

我们改写为其对偶线性规划,即变成:

\[\max \sum_u a_u(s_u-t_u)\\ s.t.\begin{cases} \sum_{u\in S}(s_u-t_u)\le 1& (x_S)\\ s_u,t_u\ge 0\\ \end{cases} \]

不难看出,我们可以令 \(b_u=s_u-t_u\),那么就相当于 \(b_u\) 可任取,然后目标是最大化 \(\sum a_ub_u\),满足约束

\[\forall S\in \mathcal I,\sum_{u\in S} b_u \le 1\\ \]

根据线性规划的对偶定理,这两个问题的解是相等的,我们只需求解后者。

由此,我们可以自然地设计一个 \(\text{DP}\)\(f_{u,d}\) 表示 \(u\) 这颗子树里从 \(u\) 出发,\(b\) 值之和最大的路径为 \(d\) 时最大的 \(\sum a_ub_u\)。显然 \(d \in \{0,1\}\)。而 \(b_u\) 只有三种必要的取值:\(\{-1,0,1\}\)。我们枚举各情况进行转移,即是一个 \(O(n)\) \(\text{DP}\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int op,n;
int a[3000005];
int ver[6000005],ne[6000005],head[3000005],cnt;
inline void link(int x,int y){
	ver[++cnt]=y;
	ne[cnt]=head[x];
	head[x]=cnt;
}
long long dp[2][6000005];
void dfs(int x,int fi){
	long long tmp[3]={0,0,0};
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		if(u==fi)continue;
		dfs(u,x);
		long long pre[3]={tmp[0],tmp[1],tmp[2]};
		tmp[0]+=dp[0][u];tmp[1]=max(pre[1]+dp[0][u],pre[0]+dp[1][u]);
		tmp[2]=max(pre[2]+max(dp[0][u],dp[1][u]),pre[1]+dp[1][u]);
	}
	dp[0][x]=max(tmp[0],max(tmp[1],tmp[2])-a[x]);dp[1][x]=max(tmp[1],tmp[0]+a[x]);
}
namespace Generate{
	int n,seed;
	static const int mod=1e9;
	inline int Rand(int x){
		seed=(1ll*seed*0x66CCF+19260817ll)%x+1;
		seed=(1ll*seed*0x77CCF+20060428ll)%x+1;
		seed=(1ll*seed*0x88CCF+12345678ll)%x+1;
		seed=(1ll*seed*0x33CCCCFF+10086001ll)%x+1;
		return seed;
	}
	inline void RS(int* a){
		int cnt=0;
		for(int i=1;i<=n;i++)a[i]=Rand(mod); 
		for(int i=2;i<=n;i++){
			int fa=Rand(i-1);
			link(fa,i);link(i,fa);
		}
	}
};
int main(){
	scanf("%d",&op);
	if(op==1){
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int i=1;i<n;i++){
			int x,y;scanf("%d%d",&x,&y);
			link(x,y);link(y,x);
		}
	}else{
		scanf("%d %d",&Generate::seed,&n);
		Generate::n=n;
		Generate::RS(a);
	}
	dfs(1,1);
	printf("%lld\n",max(dp[0][1],dp[1][1]));

	return 0;
}

[ZJOI2020] 序列

运用类似的方式,转化对偶问题,然后直接 \(\text{DP}\),设状态 \(f_{i,x,y,z}\) 表示考虑到 \(i\),以 \(i\) 为右端点的三类操作的 \(b_i\) 之和的最大值分别为 \(x,y,z\) 时,\(\sum_{j=1}^i\limits a_j b_j\) 的最大值。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int A[100005];
long long dp[100005][2][2][2];
inline void solve(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)scanf("%d",&A[i]);
	for(int i=0;i<=n;i++){
		for(int a=0;a<2;a++){
			for(int b=0;b<2;b++){
				for(int c=0;c<2;c++)dp[i][a][b][c]=-1e18;
			}
		}
	}
	dp[0][0][0][0]=0;
	for(int i=0;i<n;i++){
		for(int a=0;a<2;a++){
			for(int b=0;b<2;b++){
				for(int c=0;c<2;c++){
					for(int t=-1;t<=1;t++){
						int ua=max(max(t,a+t),0),ub=b,uc=c;
						if(i&1)ub=max(max(t,b+t),0);
						else uc=max(max(t,c+t),0);
						if(ua>1||ub>1||uc>1)continue;
						dp[i+1][ua][ub][uc]=max(dp[i+1][ua][ub][uc],dp[i][a][b][c]+t*A[i]);
					}
				}
			}
		}
	}
	long long res=0;
	for(int a=0;a<2;a++){
		for(int b=0;b<2;b++){
			for(int c=0;c<2;c++)res=max(res,dp[n][a][b][c]);
		}
	}
	printf("%lld\n",res);
}
int main(){
	scanf("%d",&T);
	while(T--)solve();

	return 0;
}



posted @ 2022-06-08 18:21  一粒夸克  阅读(395)  评论(0编辑  收藏  举报