luogu3558题解
这道题严谨证明略显棘手,不过好在递推式相对好猜一些然后我就猜过了。
由于是左边的数影响右边,所以我们来到开头进行讨论。
假如第一个数为 \(1\),那么后面的数都可以变大至整数,有解。
假如第一个数为 \(-1\),那么可以忽略这个数看是否有解。
假如第一个数为 \(0\),但后面有一个 \(-1\),则无解。
因此解决掉判是否有解的问题。
因为是最优化原则,这个问题需要用动态规划解决。
这个题有一个性质,一定存在有一个方案使得修改后的所有数 \(a_i\in\{-1,0,1\}\)。
证明:
- \(a_i<-1\):\(a_1\) 显然不成立。\(a_i,i\in\left[2,n\right]\) 不能小于 \(a_1\)。
- \(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_{i,-1}\) 需要序列所有的数都小于等于 \(-1\),所以直接由 \(dp_{i-1,-1}\) 的情况转移过来,把 \(a_{i}\) 修改为 \(-1\) 的代价累加即可。
\(dp_{i,0}\) 可以是前面为 \(0\) 或 \(-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}\) 要多很多。 -
当 \(a_i\) 为 \(0\) 时,从前面的 \(dp_{i-1,-1}\) 和 \(dp_{i-1,-1}\) 中取最小值即可。
显然不需要修改,而且我们需要一个满足题意的条件,故选择这两种情况。 -
当 \(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}\) 前面是啥数都行。
- 当 \(a_i\) 为 \(1\) 时,选前面的三种情况转移就行了。
- 当 \(a_i\) 为 \(0\) 时,从 \(dp_{i-1,1}\) 代价加 \(1\) 转移即可。
- 当 \(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;
}