1. 程式人生 > 其它 >題解 Loj10151 【分離與合體】

題解 Loj10151 【分離與合體】

題目傳送門

更好的閱讀體驗

簡單寫一寫好了,其實挺板的。

題目涉及區間拆解和合並,顯然是一個區間 Dp。每次將一個區間分成左右兩部分,並加上額外的收益。於是得出方程:

\[dp_{l,r}=\max\{(a_l+a_r)\times a_j+dp_{l,j}+dp_{j+1,r}\} \]

解釋一下:

\((a_l+a_r)*a_j\):這一部分就是拆解區間額外的收益。

\(dp_{l,j}+dp_{j+1,r}\):分成左右兩個小區間,遞迴處理。然後由小區間組合得出大區間的答案。

這一部分直接搜尋即可。

int dfs(int l,int r){
	if(l>r)return 0;
	if(~dp[l][r])return dp[l][r];//記憶化 
	dp[l][r]=0;
	F(j,l,r-1){
		int tmp=a[j]*(a[l]+a[r])+dfs(l,j)+dfs(j+1,r);
		
		if(tmp>dp[l][r])dp[l][r]=tmp,rec[l][r]=j;
	}
	return dp[l][r];
}

然後噁心的地方在於輸出答案。

先列印一分為二的區域,然後從左到右列印二分為四的分離區域,然後是四分為八的……

觀察這段話,發現實際上類似於 bfs,先輸出同層次的答案。於是用佇列模擬 bfs 即可(其實不噁心)

void output(){
	queue<P> q;
	q.push({1,n});
	while(!q.empty()){
		P x=q.front();
		q.pop();
		if(x.l==x.r)continue;
		printf("%d ",rec[x.l][x.r]);
		q.push({x.l,rec[x.l][x.r]});
		q.push({rec[x.l][x.r]+1,x.r});
	}
}

最後給出完整程式碼:

#include<bits/stdc++.h>
#define reg register
#define F(i,a,b) for(reg int i=a;i<=b;++i)
using namespace std;
inline int read();
const int N=305;
int n,a[N],dp[N][N],rec[N][N];
struct P{
	int l,r;
};
int dfs(int l,int r){
	if(l>r)return 0;
	if(~dp[l][r])return dp[l][r];//記憶化 
	dp[l][r]=0;
	F(j,l,r-1){
		int tmp=a[j]*(a[l]+a[r])+dfs(l,j)+dfs(j+1,r);
		
		if(tmp>dp[l][r])dp[l][r]=tmp,rec[l][r]=j;
	}
	return dp[l][r];
}
void output(){
	queue<P> q;
	q.push({1,n});
	while(!q.empty()){
		P x=q.front();
		q.pop();
		if(x.l==x.r)continue;
		printf("%d ",rec[x.l][x.r]);
		q.push({x.l,rec[x.l][x.r]});
		q.push({rec[x.l][x.r]+1,x.r});
	}
}
int main(){
	memset(dp,-1,sizeof(dp));
	n=read();
	F(i,1,n)a[i]=read();
	printf("%d\n",dfs(1,n));
	output();	
	return 0;
}
inline int read(){
	reg int x=0;
	reg char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x;
}

AC

歡迎交流討論,請點個贊哦~