[CSP-S模拟测试]:集合合并(记忆化搜索)

题目传送门(内部题133)


输入格式

  第一行一个正整数$n$。
  第二行$n$个正整数$a_i$,表示一开始有$S_i=\{a_i\}$


输出格式

  输出一个非负整数表示最大的收益之和


样例

样例输入:

4
1 2 2 2

样例输出:

5


数据范围与提示

样例解释:

  先合并第一个和第二个,得到$\{1,2\},\{2\},\{2\}$,产生的收益为$1$
  再合并第一个和第二个,得到$\{1,2\},\{2\}$,产生的收益为$2$
  再合并这两个,得到$\{1,2\}$,产生的收益为$2$
  所以答案是$5$

数据范围:

  对于$30\%$的数据,有$1\leqslant n\leqslant 7$
  对于$50\%$的数据,有$1\leqslant n\leqslant 50$
  对于$100\%$的数据,有$1\leqslant n\leqslant 300,1\leqslant a_i\leqslant n$


题解

感觉记忆化搜索比区间$DP$好打一些。

设$dp[l][r]$表示合并完$[l,r]$的最大收益,$con[l][r]$表示$[l,r]$区间内元素的个数($\Theta(n^3)$预处理即可,当然也可以用$bitset$做到$\Theta(\frac{n^3}{\omega})$,或者主席树做到$\Theta(n^2\log n)$)。

转移也很简单:

$$dp[l][r]=\max\limits_{i=l}^{r-1}dp[l][i]+dp[i+1][r]+con[l][i]\times con[i+1][r]$$

处理环的话无非就是断环为链即可。

时间复杂度:$\Theta(n^3)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
int n;
int a[601],con[601][601];
long long dp[601][601],ans;
bitset<300> v[601][601];
long long dfs(int l,int r)
{
	if(l==r)dp[l][r]=0;
	if(dp[l][r]!=-1)return dp[l][r];
	for(int i=l;i<r;i++)
		dp[l][r]=max(dp[l][r],dfs(l,i)+dfs(i+1,r)+1LL*con[l][i]*con[i+1][r]);
	return dp[l][r];
}
int main()
{
	memset(dp,-1,sizeof(dp));
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i+n]=a[i];
	}
	for(int i=1;i<=2*n;i++)
	{
		v[i][i][a[i]]=con[i][i]=1;
		for(int j=i+1;j<=2*n;j++)
		{
			v[i][j]=v[i][j-1];
			v[i][j][a[j]]=1;
			con[i][j]=v[i][j].count();
		}
	}
	for(int i=1;i<=n;i++)ans=max(ans,dfs(i,i+n-1));
	printf("%lld\n",ans);
	return 0;
}

rp++

猜你喜欢

转载自www.cnblogs.com/wzc521/p/11824332.html