【题解】分离与合体 [Loj10151]

【题解】分离与合体 [Loj10151]

传送门:分离与合体 \([Loj10151]\)

【题目描述】

给定一个长度为 \(n\) 的序列,如果从某个点 \(k\) 处将区间 \([l,r]\) 断开,划分为 \([l,k]\)\([k+1,r]\),可以得到 \(a[k]*(a[l]+a[r])\) 的分数,要求最后要把区间划分到无法再分为止(即长度全为 \(1\)),并按照分离时间从前到后,区间从左到右的顺序输出所有划分点 \(k\)

【样例】

样例输入:
7
1 2 3 4 5 6 7

样例输出:
238
1 2 3 4 5 6

【数据范围】

\(20\%\) \(N \leqslant 10\)

\(40\%\) \(N \leqslant 50\)

\(100\%\) \(N \leqslant 300\)


【分析】

与经典区间 \(dp\)石子合并 \([NOI1995]\) \([P1880]\)非常相似,用 \(dp[l][r]\) 表示将 \([l,r]\) 这段区间彻底划分完所能获得的最大分数。

通常区间 \(dp\) 有两种大的方向:
\((1).\)小区间大区间转移
\((2).\)大区间小区间转移

这道题应采用 \((1)\) 更为合适。

按区间长度从小到大枚举所有区间 \([l,r]\),再枚举所有决策点 \(k \in[l,r-1]\)\(dp\) 方程为:
\(dp[l][r]=max\{(a[l]+a[r])*a[k] + dp[l][k] +dp[k+1][r] \}\)

那么再来看这无比诡异的输出。
区间最优决策点在 \(dp\) 的时候记录一下就可以,输出方式可以用队列来进行模拟,和 \(bfs\) 的过程类似。

另外要注意这里队列的两种写法会造成空间消耗的不同,
\((1).\) 从队首取出元素后判断是否为合法区间(即长度大于 \(1\) 才输出划分点)
\((2).\) 在入队前判断,仅让合法区间入队。

第二种只会在队列中加入 \(n-1\) 个合法区间(因为只会划分 \(n-1\) 次),而第一种会比第二种多加入 \(n\) 个长度为 \(1\) 的不合法区间,因此队列空间要开 \(2n\)

时间复杂度为 \(O(n^3)\)

【Code】

#include<cstdio>
#define Re register int
const int N=303;
struct QAQ{int l,r;}Q[N];//空间要开够
int n,h=1,t,tmp,a[N],g[N][N],dp[N][N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
int main(){
    in(n);
    for(Re i=1;i<=n;++i)in(a[i]);
    for(Re i=2;i<=n+1;++i)
    	for(Re l=1;l+i-1<=n;++l){
            Re r=l+i-1;
            for(Re k=l;k<r;++k)
                if((tmp=dp[l][k]+dp[k+1][r]+a[k]*(a[l]+a[r]))>dp[l][r])dp[l][r]=tmp,g[l][r]=k;
    	}
    printf("%d\n",dp[1][n]);
    Q[++t]=(QAQ){1,n};
    while(h<=t){
    	QAQ x=Q[h++];
    	Re l=x.l,r=x.r,k=g[l][r];
//    	if(l==r)continue;/*写法1.0*/ 
//      if(!k)continue;/*写法1.1*/ 
    	printf("%d ",k);
//    	Q[++t]=(QAQ){l,k};/*写法1*/ 
//    	Q[++t]=(QAQ){k+1,r};/*写法1*/ 
        if(l<k)Q[++t]=(QAQ){l,k};/*写法2*/ 
        if(k+1<r)Q[++t]=(QAQ){k+1,r};/*写法2*/ 
    }
}
posted @ 2019-08-15 16:30  辰星凌  阅读(574)  评论(0编辑  收藏  举报