luogu3558题解

这道题严谨证明略显棘手,不过好在递推式相对好猜一些然后我就猜过了。

由于是左边的数影响右边,所以我们来到开头进行讨论。

假如第一个数为 \(1\),那么后面的数都可以变大至整数,有解。
假如第一个数为 \(-1\),那么可以忽略这个数看是否有解。
假如第一个数为 \(0\),但后面有一个 \(-1\),则无解。

因此解决掉判是否有解的问题。

因为是最优化原则,这个问题需要用动态规划解决。

这个题有一个性质,一定存在有一个方案使得修改后的所有数 \(a_i\in\{-1,0,1\}\)
证明:

  1. \(a_i<-1\)\(a_1\) 显然不成立。\(a_i,i\in\left[2,n\right]\) 不能小于 \(a_1\)
  2. \(a_i>1\)\(a_1\) 显然不成立。首先将这个数变大需要至少付出 \(1\) 的代价,如果让后面的数满足不下降序列的话,\(0,1\) 都需要付出 \(1\) 的代价,\(-1\) 需要付出 \(2\) 的代价,与 \(a_i=1\) 时付出代价相同。

这个序列最后会变成一个单调不降的序列,我们可以将末尾的数分为 \(-1\)\(0\)\(1\)​,分别对子序列进行转移,最后来取最优结果。

我们令 \(dp_{i,j}\) 表示以 \(1\) 开头以 \(i\) 结尾的子序列最后一个元素为 \(j\) 的最小代价。
(实际的实现中 \(j\) 作为 \(-1\) 时不能做数组的下标,我们可以在实现的时候采用 \(j+1\)

设初始的序列为 \(a\)

首先是开头的初始状态,由于 \(a_1\) 不可修改,所以有:

\[dp_{1,j}=\begin{cases}+\infty,&j\ne a_1,\\0,&j=a_1.\\\end{cases} \]

然后是递推的时候需要分类讨论。
(跟其他题解的讨论不是很相同,但是展开是一样的)

由于 \(dp_{i,-1}\) 需要序列所有的数都小于等于 \(-1\),所以直接由 \(dp_{i-1,-1}\) 的情况转移过来,把 \(a_{i}\) 修改为 \(-1\) 的代价累加即可。

\(dp_{i,0}\) 可以是前面为 \(0\)\(-1\)​。

  1. \(a_i\)\(1\) 时,必须有修改后的 \(a_{i-1}=-1\)\(a_i\) 修改为 \(0\)​。
    可以由 \(dp_{i-1,-1}\) 转移过来,该情况为最优情况。
    因为该位置是 \(0\),所以前面的数要小于等于 \(0\) 才能满足条件,如果要让 \(a_i\) 前面一堆 \(0\) 的话需要推过来很多 \(1\) ,然后再推过来很多 \(0\) 付出的代价比 \(dp_{i-1,-1}\) 要多很多。

  2. \(a_i\)\(0\) 时,从前面的 \(dp_{i-1,-1}\)\(dp_{i-1,-1}\)​ 中取最小值即可。
    显然不需要修改,而且我们需要一个满足题意的条件,故选择这两种情况。

  3. \(a_i\)\(-1\) 时,必须有 \(a_{i-1}=1\)\(a_i\) 修改为 \(0\)
    这个情况好像很难取到,本来是写了 dp[i][0+1]=dp[i-1][1+1]+1+((i-p1)+dp[p1][-1+1]);//p1是i前面最后一个1的位置,然后我尝试下直接赋值 0x3f3f3f3f 也过了。
    这个递推式我觉得是错的。我猜是代价太大取不到。
    感性理解一下应该是进一步修改 \(a_i\)\(1\) 能满足题目要求而且花费代价小,所以这一步取不到。

\(dp_{i,1}\) 前面是啥数都行。

  1. \(a_i\)\(1\) 时,选前面的三种情况转移就行了。
  2. \(a_i\)\(0\) 时,从 \(dp_{i-1,1}\) 代价加 \(1\) 转移即可。
  3. \(a_i\)\(-1\) 时,从 \(dp_{i-1,1}\) 代价加 \(2\) 转移即可。

然后这个递推式就搞完了。后面码码码就行了。

代码如下。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int MAXN=1e6+10;
int n,a[MAXN],pf1,p1,dp[MAXN][5];
template<typename T>
T read(){
	T f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+(ch^48);ch=getchar();}
	return f*x;
}
namespace sol{
	void solve(){
		n=read<int>();
		for(int i=1;i<=n;++i){
			a[i]=read<int>();
			if(a[i]<0&&!pf1)pf1=i;
			else if(a[i]&&!p1)p1=i;
		}
		for(int i=2;i<p1;++i){
			if(a[i-1]==0&&a[i]==-1){
				puts("BRAK");
				return;
			}
		}
		for(int i=0;i<=1;++i){
			dp[1][i+1]=0x3f3f3f3f;
		}
		dp[1][a[1]+1]=0;
		for(int i=2;i<=n;++i){
			//-1结尾最小代价 
			dp[i][-1+1]=dp[i-1][-1+1]+(a[i]+1);
			//0的 
			if(a[i]==0)dp[i][0+1]=min(dp[i-1][-1+1],dp[i-1][0+1]);
			else if(a[i]==1)dp[i][0+1]=dp[i-1][-1+1]+1;
			else dp[i][0+1]=0x3f3f3f3f;//懒得推
			//1的
			if(a[i]>0)dp[i][1+1]=min(dp[i-1][-1+1],min(dp[i-1][0+1],dp[i-1][1+1]));
			else if(p1<i)dp[i][1+1]=dp[i-1][1+1]+(1-a[i]);
			else dp[i][1+1]=0x3f3f3f3f;
//			printf("%d :%d %d %d\n",i,dp[i][-1+1],dp[i][0+1],dp[i][1+1]);
			if(a[i]>0)p1=i;
			else if(a[i]<0)pf1=i;
		}
		printf("%d\n",min(dp[n][-1+1],min(dp[n][0+1],dp[n][1+1])));
	}
}
int main(){
	sol::solve();
	return 0;
}
posted @ 2024-02-05 11:53  LiJoQiao  阅读(32)  评论(2编辑  收藏  举报