题解 P3558 【[POI2013]BAJ-Bytecomputer】
NKU_AI_HMX · · 题解
这篇题解主要是对其他题解做出补充和优化,解决大家的一些问题,帮助大家理解。
目录
-
思路简述
-
状态转移方程
-
如何优化
-
最终代码
正文
1. 思路简述
动态规划或者贪心,记忆搜索都可,其他题解都有说明,这里我主要说动态规划,我们构造函数
2. 状态转移
我先放出两种代码
1.
memset(f, 63, sizeof(f));
f[1][a[1] + 1] = 0;
for (int i = 2; i <= n; i++)
{
if (a[i] == -1)
{
f[i][0] = f[i - 1][0];
f[i][2] = f[i - 1][2] + 2;
}
if (a[i] == 0)
{
f[i][0] = f[i - 1][0] + 1;
f[i][1] = min(f[i - 1][0], f[i - 1][1]);
f[i][2] = f[i - 1][2] + 1;
}
if (a[i] == 1)
{
f[i][0] = f[i - 1][0] + 2;
f[i][1] = f[i - 1][0] + 1;
f[i][2] = min(f[i - 1][0], min(f[i - 1][1], f[i - 1][2]));
}
}
int temp = min(f[n][0], min(f[n][1], f[n][2]));
if (temp >= inf)cout << "BRAK";
else cout << temp;
2.
f[1][0]=INF,f[1][1]=INF,f[1][2]=INF;
f[1][num[1]+1]=0;
for(register int i=2;i<=n;i++)
{
if(num[i]==-1)
{
f[i][0]=f[i-1][0];
f[i][1]=(num[i-1]==1)?min(f[i-1][0],f[i-1][1])+1:INF;
f[i][2]=(num[i-1]==1)?min(f[i-1][0],min(f[i-1][1],f[i-1][2]))+2:f[i-1][2]+2;
}
if(num[i]==0)
{
f[i][0]=f[i-1][0]+1;
f[i][1]=min(f[i-1][0],f[i-1][1]);
f[i][2]=(num[i-1]==1)?min(f[i-1][0],min(f[i-1][1],f[i-1][2]))+1:f[i-1][2]+1;
}
if(num[i]==1)
{
f[i][0]=f[i-1][0]+2;
f[i][1]=(num[i-1]==-1)?min(f[i-1][0],f[i-1][1])+1:f[i-1][0]+1;
f[i][2]=min(f[i-1][0],min(f[i-1][1],f[i-1][2]));
}
}
int ans=min(f[n][0],min(f[n][1],f[n][2]));
if(ans>=INF) printf("BRAK\n");
else printf("%d\n",ans);
大家如果看了其他的题解就会发现有两种代码,第一种,转移函数十分简要,而且题解也没说明,没有第二种考虑周全,而第二种的转移函数似乎又显得十分冗长,又有些多余。
接下来我给大家解释一下其中的道理,并指出其他题解没说明白的或者说错误的地方。
第二种代码
-
num[i]==-1 if(num[i]==-1) { f[i][0]=f[i-1][0]; f[i][1]=(num[i-1]==1)?min(f[i-1][0],f[i-1][1])+1:INF; f[i][2]=(num[i-1]==1)?min(f[i-1][0],min(f[i-1][1],f[i-1][2]))+2:f[i-1][2]+2; }
啊?转移过去了?奇怪吧?其实不奇怪,上面讲的第一种代码根本没管多次来回操作的问题(就这题而言不需要管)。而第二种代码他管了,甚至它自己都不知道它管了,因为只管了一部分,而上面说的那个
如果你需要用前面一个数去改变后面一个数,而这个数本身是具有改变后面数的能力的,增加或者减少,如果是增加,当你用
同样,当我们用
-
num[i]==0 if(num[i]==0) { f[i][0]=f[i-1][0]+1; f[i][1]=min(f[i-1][0],f[i-1][1]); f[i][2]=(num[i-1]==1)?min(f[i-1][0],min(f[i-1][1],f[i-1][2]))+1:f[i-1][2]+1; }
同理这里面
-
num[i]==1 if(num[i]==1) { f[i][0]=f[i-1][0]+2; f[i][1]=(num[i-1]==-1)?min(f[i-1][0],f[i-1][1])+1:f[i-1][0]+1; f[i][2]=min(f[i-1][0],min(f[i-1][1],f[i-1][2])); }
现在我们考虑前面一个数让后面减小的情况,两种选择,减到
减到
如果减到
用
以上就说明了第二种代码一些转移的多余,精简代码看第一种。
3. 如何优化
第一,我们可以滚。我的意思是滚动数组,滚到只用开
4. 最终代码
#include<bits/stdc++.h>
#define ll long long
#define re register
#define pf putchar(' ')
#define pfn putchar('\n')
using namespace std;
char ch1;
template<class T>
inline void rd(T& x) {
x = 0; bool w = 0;
ch1 = getchar();
while (!isdigit(ch1)) { ch1 == '-' && (w = 1), ch1 = getchar(); }
while (isdigit(ch1)) { x = (x << 1) + (x << 3) + (ch1 & 15), ch1 = getchar(); }
w && (x = (~x) + 1);
}
template<class T>
inline void wr(T x)
{
if (x < 0) x = -x, putchar('-');
if (x < 10) {
putchar(x + 48);
return;
}
T L = x / 10;
wr(L);
putchar(x - ((L << 1) + (L << 3)) + 48);
}
int n,x,f[3];
int inf = 1 << 27;
int main()
{
rd(n); rd(x);
memset(f, 63, sizeof(f));
f[x + 1] = 0;
for (re int i = 2; i <= n; i++)
{
rd(x);
if (x == -1)
{
f[1] = inf;
f[2] += 2;
}
else if (x == 0)
{
f[1] = min(f[0], f[1]);
f[0]++;
f[2]++;
}
else
{
f[2] = min(f[0], min(f[1], f[2]));
f[1] = f[0] + 1;
f[0] += 2;
}
}
int temp = min(f[0], min(f[1], f[2]));
if (temp >= inf)printf("BRAK");
else wr(temp);
return 0;
}
(如果有情况未覆盖,参照上面的思路可自行推理)