[ZJOI2020] 序列 线性规划做法/贪心做法

2023-06-13,

线性规划做法

同时也作为线性规划对偶的一个小小的学习笔记。

以下 \(\cdot\) 表示点积,\(b,c,x,y\) 是行向量。

\(A\) 是矩阵,对于向量 \(u,v\) 若 \(\forall i,u_i\leq v_i\) 则称 \(u\leq v\),\(\geq\) 同理。

线性规划标准型:

\[\max c\cdot x\\
s.t.
\left\{
\begin{aligned}
&Ax\leq b \\
&x\geq 0
\end{aligned}
\right.
\]

它的对偶是:

\[\min b\cdot y \\
s.t.
\left\{
\begin{aligned}
&A^Ty\geq c \\
&y\geq 0
\end{aligned}
\right.
\]

弱对偶定理:\(\max c\cdot x \leq \min b\cdot y\)。

我们可以任意取可行域中的 \(x,y\),然后对于 \(y^TAx\) 一方面由于 \(y^T\) 非负有 \(y^TAx\leq y^T b=b\cdot y\),另一方面 \(y^TAx=(yA^T)^Tx\geq c^Tx=c\cdot x\),即得证。

强对偶定理:\(\max c\cdot x = \min b\cdot y\)。也就是说我们可以把线性规划转为其对偶问题进行求解。

回到这道题,给每一个区间的每一种操作编号 \(1\sim M\),对于第 \(t\) 个操作设其下标集合为 \(I_t\),我们建一个变量 \(x_t\),表示这个操作重复做的次数。我们放宽 \(x_t\in R\),可以证明最优解仍然是整数。

将取等条件拆成两个条件,那么答案是:

\[\min \sum_{t=1}^M x_t\\
s.t.
\left\{
\begin{aligned}
&\sum_{t=1}^{M} [i\in I_t] x_t\geq a_i \\
&-\sum_{t=1}^{M} [i\in I_t] x_t\geq -a_i \\
&x_t\geq 0
\end{aligned}
\right.
\]

对偶一下,有:

\[\max \sum_{i=1}^n a_i(p_i-q_i)\\
s.t.
\left\{
\begin{aligned}
&\sum_{i=1}^{n} [i\in I_t](p_i-q_i)\leq 1 \\
&p_i,q_i\geq 0
\end{aligned}
\right.
\]

容易发现 \(p_i-q_i\) 可以取任意实数,设 \(y_i=p_i-q_i\),有:

\[\max \sum_{i=1}^n a_i y_i\\
s.t. \sum_{i=1}^{n} [i\in I_t]y_i\leq 1
\]

这里 yhx 大佬的博客中讲此矩阵是全单模矩阵,也就是说线性规划可行域的顶点全是整数所以这个线性规划具有整数性?不太了解什么是全单模 QAQ。

既然有整数性,那么考虑拿着这个东西做。该线性规划的实际意义是让我们对每个位置赋权,使得每种操作下标的权值之和都 \(\leq 1\)。

经典套路,类似最大子段和,记录三种操作后缀和的最大值 \(x,y,z\) 来 DP。由限制有 \(x,y,z\leq 1\)。而如果有 \(x/y/z<0\),那么我们就需要”截断“然后新开一个后缀。同时这也说明了 \(y_i\) 有意义的取值只有 \(0,1,-1\),\(<-1\) 的都是”截断“而总权值没有 \(-1\) 优秀所以一定不会取。

时间复杂度 \(O(n)\) 带 \(24\) 的常数。

#include <cstdio>
#include <algorithm>
#define forxyz \
for(int x=0;x<2;++x) \
for(int y=0;y<2;++y) \
for(int z=0;z<2;++z)
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
typedef long long ll;
const int N=100003;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n;
int a[N];
ll f[N][2][2][2];
void solve(){
n=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) forxyz f[i][x][y][z]=-INF;
f[0][0][0][0]=0;
for(int i=1;i<=n;++i){
forxyz{
for(int d=-1;d<=1;++d){
if(x+d>1||y+d>1) continue;
ll &dp=f[i][max(x+d,0)][z][max(y+d,0)];
dp=max(dp,f[i-1][x][y][z]+d*a[i]);
}
}
}
ll res=0;
forxyz res=max(res,f[n][x][y][z]);
printf("%lld\n",res);
}
int main(){
int tc=read();
while(tc--) solve();
return 0;
}

贪心做法

解析咕了,具体看洛谷题解区。

#include <cstdio>
#include <algorithm>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
typedef long long ll;
void solve(){
int n=read();
ll a=0,b=0,c=0,res=0,las=read();
for(int i=2;i<=n+1;++i){
ll x=0;
if(i<=n) x=read();
ll dlt=0;
if(x<a+b){
dlt=a+b-x;
if(a<dlt) b-=dlt-a,dlt=a;
if(b<dlt) a-=dlt-b,dlt=b;
a-=dlt;b-=dlt;x-=dlt;
}
x-=a+b;
ll mn=min(las,x);
res+=mn;las-=mn;x-=mn;a+=mn;
res+=las;c+=las;
x+=dlt;res-=dlt;
las=x;
swap(b,c);
}
printf("%lld\n",res);
}
int main(){
int tc=read();
while(tc--) solve();
return 0;
}

[ZJOI2020] 序列 线性规划做法/贪心做法的相关教程结束。

《[ZJOI2020] 序列 线性规划做法/贪心做法.doc》

下载本文的Word格式文档,以方便收藏与打印。