线性规划与对偶问题
首先,讲下什么是线性规划。具有如下形式:
即为一个线性规划问题(它是标准形式,也就是只要能化成这种形式的都是线性规划问题)。
现在我们讲讲什么是对偶问题。
对偶问题的标准形式:
原问题:
对于一个矩阵 \(A\) ,列向量 \(c,b\) ,求:
对偶问题:
其中列向量 \(x,y\) 是两个变量。
P7246 手势密码
我们考虑将问题描述为线性规划,设集族 \(\mathcal I\) 包含了所有的路径,那么我们无非要给每条路径 \(S\) 赋予一个权值 \(x_S\) 表示这条路径走了多少次,容易写出约束:
我们的目的就是求:
线性规划一般是用不等式表述的,所以我们先将前面的等式拆成两组不等式,就可以写成标准的形式:
我们改写为其对偶线性规划,即变成:
不难看出,我们可以令 \(b_u=s_u-t_u\),那么就相当于 \(b_u\) 可任取,然后目标是最大化 \(\sum a_ub_u\),满足约束
根据线性规划的对偶定理,这两个问题的解是相等的,我们只需求解后者。
由此,我们可以自然地设计一个 \(\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;
}