紫书DP课后题最后一题了,也算是把这一部分做完了吧。
这个题,在uva上的数据太水了,导致很多网上的题解代码是错误的。在gym10128H这里会wa。这些题解大多是用一个简单的贪心策略处理分段,然后用区间dp处理分出来的段落。但是实际上分段也需要用一个dp,比如数据 1 2 4 3 1 2,用简单的贪心分段就会把4 3分到前面那一段,这样的出来结果是5(当把1 2 放进4 3里的时候4和3都要打开),但是如果把4 3分到后面的1 2里答案是4,是更优的解。
先讲简单的部分:分好段之后的区间dp。这个就是普通的区间dp $$dp(l,r)=min(dp(l,i)+dp(i+1,r)+cost(l,i,i+1,r)),i\in[l,r)$$其中 \[dp(l,r)\]表示
[l,r]之间的套娃组装成1个所需要的最小花费,
\[cost(l,i,i+1,r)\]表示把这两段合并起来的花费。设mi(l,r)为\[[l,r]\]之间的最小值,则不难发现cost就是
\[[l,r]\]中的元素数量-(除了\[max(mi(l,i),mi(i+1,r))\]所在的一段的另外一段中的小于\[max(mi(l,i),mi(i+1,r))\]的数的个数) (可能说起来有点绕但是画几组样例很好理解)。这样可以递推预处理出来所有的mi(i,j),然后每次dp的时候计算cost。(爆炸的复杂度
然后再说这个题比较难的也是有意思的部分:如何分段。这个让我没想到的骚操作是我一直感觉应该先分段再dp,这里竟然是先区间dp再分段。想了想才明白,区间dp其实只需要保证dp的对象区间满足他是从1开始的连续的一段数字(比如 1 3 2 4就是,而1 4 2不是)就可以得出正确的结果,那么我们可以先把所有的满足这种条件的段的区间dp值都处理出来,这里最精妙的一点就是如何判断这一段区间是满足这个条件的,其实只要满足两个条件(选这两个条件是因为比较好处理,这里如何预处理出来区间中数字的唯一性我也是从dalao的题解中学到的,细节看代码中is_unique数组的计算)
①区间的最大值等于区间的长度
②区间里面的元素都是独一无二的
这样的话还看上面的1 2 4 3 1 2这组数据,就会有两个\[ dp(l,r) \]覆盖到4 3这一段,其值都已经被正确的计算出来。这时候我们可以再通过一个线性dp来决定最优解\[f[i]=min(f[j]+dp[j+1][i]),j\in[0,i),i\in[1,n] \]这样就可以完整的解决这个题目了。
代码:
#include <bits/stdc++.h> using namespace std; const int maxn = 500 + 15; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; const int maxnode = 1e6 + 5; typedef long long LL; typedef unsigned long long ULL; int n,ori[maxn]; int pre[maxn][maxn][2],dp[maxn][maxn],fdp[maxn];; int mi[505][505],mx[505][505]; int tmp[maxn]; int dps(int l,int r) { if(l>=r) return 0; else { if(dp[l][r]!=INF) return dp[l][r]; int best=INF,tt,nu=0; for(int i=l;i<r;i++) { nu=0; if(mi[l][i]<mi[i+1][r]) { tt=mi[i+1][r]; for(int k=l;k<=i;k++) nu+=(ori[k]<tt); } else { tt=mi[l][i]; for(int k=i+1;k<=r;k++) nu+=(ori[k]<tt); } best=min(best,r-l+1-(nu)+dps(l,i)+dps(i+1,r)); } return dp[l][r]=best; } } int bf[maxn],cur[maxn],is_unique[maxn][maxn]; void precal(int n) { memset(dp,INF,sizeof(dp)); memset(mx,0,sizeof(mx)); memset(mi,INF,sizeof(mi)); memset(cur,0,sizeof(cur)); memset(fdp,INF,sizeof(fdp)); for(int i=1;i<=n;i++) { bf[i]=cur[ori[i]]; cur[ori[i]]=i; mx[i][i]=ori[i],mi[i][i]=ori[i],is_unique[i][i]=1,dp[i][i]=0; } for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) { mx[i][j]=max(mx[i][j-1],ori[j]); mi[i][j]=min(mi[i][j-1],ori[j]); is_unique[i][j]=(is_unique[i][j-1]&&bf[j]<i); } } int main() { // ios::sync_with_stdio(0); // cin.tie(0), cout.tie(0); while(~scanf("%d",&n)) { for(int i=1;i<=n;i++) scanf("%d",&ori[i]); precal(n); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) if(j-i+1==mx[i][j]&&is_unique[i][j]) dps(i,j); fdp[0]=0; for(int i=1;i<=n;i++) for(int j=0;j<i;j++) if(i-j==mx[j+1][i]&&is_unique[j+1][i]) fdp[i]=min(fdp[i],fdp[j]+dp[j+1][i]); if(fdp[n]==INF) puts("impossible"); else { printf("%d\n",fdp[n]); } } return 0; }