[区间DP]石子合并极其变种问题(环形,40000堆型)P1880 [NOI1995]石子合并+[Sdoi2008]石子合并/poj1738An old Stone Game

 有N堆石子,现要将石子有序的合并成一堆,规则如下:

(1)每次只能移动任意相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆总花费,要求N<=300。

变形一:(2)每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)要求N<=40000。

变形二:(3)问题(2)的是在石子排列是直线情况下的解法,石子改为环形排列

题目:

题目限制

时间限制 内存限制 评测方式 题目来源
1000ms 256000KiB 远程评测 CodeVS

题目描述 Description

  在一个操场上摆放着一排N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

  试设计一个算法,计算出将N堆石子合并成一堆的最小得分。

输入描述 Input Description

  第一行是一个数N。

  以下N行每行一个数A,表示石子数目。

#include 
#include 
#include 
 
using namespace std;
const int N = 50005;
 
int stone[N];
int n,t,ans;
 
void combine(int k)
{
    int tmp = stone[k] + stone[k-1];
    ans += tmp;
    for(int i=k;i0 && stone[j-1] < tmp;j--)
        stone[j] = stone[j-1];
    stone[j] = tmp;
    while(j >= 2 && stone[j] >= stone[j-2])
    {
        int d = t - j;
        combine(j-1);
        j = t - d;
    }
}
 
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(n == 0) break;
        for(int i=0;i= 3 && stone[t-3] <= stone[t-1])
                combine(t-2);
        }
        while(t > 1) combine(t-1);
        printf("%d\n",ans);
    }
    return 0;
}

 

 

输出描述 Output Description

  共一个数,即N堆石子合并成一堆的最小得分。

样例输入 Sample Input

4

1

1

1

1

样例输出 Sample Output

8

数据范围及提示 Data Size & Hint

对于 30% 的数据,1≤N≤100

对于 60% 的数据,1≤N≤1000

对于 100% 的数据,1≤N≤40000

对于 100% 的数据,1≤A≤200

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

区间DP 做法(要求N<=300)算法复杂度O(N^3):

#include 
#include 
#include 
#include 
#include 
#include 

#define frein freopen("D:\\cprogram\\acmprogram\\input", "r", stdin)
#define freout freopen("D:\\cprogram\\acmprogram\\ouput", "w", stdout)
#define MAXN 1005
#define MAXM 500005
#define INF 1000000000
using namespace std;
int f[309][309];
int f2[309][309];
int a[309],sum[309];

int main(int argc, char const *argv[])
{
	int n;
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d",&a[i]);
		a[i+n] = a[i];
	}
	memset(f,0x3f,sizeof(f));
	memset(f2,0,sizeof(f2));
	
	for (int i = 1; i <= n; ++i)
	{
		f2[i][i] = 0;
		f[i][i] = 0;
		sum[i]=sum[i-1]+a[i];
	}

	for (int len = 2; len <= n; ++len)
	{
		for (int l = 1; l <= n-len+1; ++l)
		{
			int r = l+len-1;
			for (int k = l; k < r ; ++k)
			{
//				printf("===f[%d][%d]%d  f[%d][%d]%d\n",l,k,f[l][k],k+1,r,f[k+1][r]);
				f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]);
				f2[l][r] = max(f2[l][r],f2[l][k]+f2[k+1][r]);
			}	
				f[l][r]+=sum[r]-sum[l-1];
				f2[l][r]+=sum[r]-sum[l-1];
		}
	}
	printf("%d\n",f[1][n]);
	return 0;
}

进行四边形优化,原状态转移方程中的k的枚举范围便可以从原来的(i~j-1)变为(s[i,j-1]~s[i+1,j])。算法复杂度进一步接近O(n^2)(逃~~~ ε=ε=ε=┏(゜ロ゜;)┛;

可以优化为O(N^2)的时间复杂度的情况,

From 黑书

凸四边形不等式:w[a][c]+w[b][d]<=w[b][c]+w[a][d](a

区间包含关系单调: w[b][c]<=w[a][d](a

定理1:  如果w同时满足四边形不等式和决策单调性 ,则f也满足四边形不等式

定理2:  若f满足四边形不等式,则决策s满足 s[i][j-1]<=s[i][j]<=s[i+1][j]

定理3: w为凸当且仅当w[i][j]+w[i+1][j+1]<=w[i+1][j]+w[i][j+1]   

 

简要证明:

若w[a][c]+w[b][d]<=w[b][c]+w[a][d],归纳证明f[a][c]+f[b][d]<=f[b][c]+f[a][d]

  设f[a][d]最优决策是在s取到,f[b][c]最优决策在t取到,设s

  可知a

    f[a][c]+f[b][d]<=f[a][s]+f[s+1][c]+w[a][c] + f[b][t]+f[t+1][d]+w[b][d]

            =f[a][s]+f[s+1][c]+w[a][d] + f[b][t]+f[t+1][d]+w[b][c]

           <=f[a][s]+w[a][d]+f[s+1][d] + f[b][t]+w[b][c]+f[t+1][c]         归纳得到 sc+td

           =f[a][d]+f[b][c]

得证.

若f[a][c]+f[b][d]<=f[b][c]+f[a][d],则s[i][j-1]<=s[i][j]<=s[i+1][j]

  仅证s[i][j-1]<=s[i][j],右边同理

  记f_k[i][j]=f[i][k]+f[k+1][j]+w[i][j]

  记s点为[i,j]最优点,t点为[i,j+1]最优点,

  则只需证明 在[i,j+1]决策时, 取s点能够比取在k∈[i,s-1]的点更优即可

    即证明 f_s[i,j+1]<=f_k[i,j+1]

  又因为f_s[i,j]<=f_k[i,j]

     只需证明 0 <= f_k[i,j] - f_s[i,j] <= f_k[i,j+1] - f_s[i,j+1]

      可发现右边即 f_k[i,j] + f_s[i,j+1] <= f_k[i,j+1] + f_s[i,j]  

      展开后即: f[k][j] + f[s][j+1] <= f[k][j+1] + f[s][j]

      正是 k

得证.

 

一般利用定理3证明凸函数,然后利用定理2的结论 s[i][j-1]<=s[i][j]<=s[i+1][j]

  就能够使得复杂度由O(n^3)降低为O(n^2)

详细证明参见《动态规划算法的优化技巧》--毛子青(会因为论文用i,j,i',j'搞得雾水,但是慢慢推一下就能够出来)

#include 
#include 
#include 
#include 
#include 
#include 

#define frein freopen("D:\\cprogram\\acmprogram\\input", "r", stdin)
#define freout freopen("D:\\cprogram\\acmprogram\\ouput", "w", stdout)
#define MAXN 1005
#define MAXM 500005
#define INF 1000000000
using namespace std;
int f[309][309];
int ss[309][309];
int a[309],sum[309];

int main(int argc, char const *argv[])
{
	int n;
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d",&a[i]);
		a[i+n] = a[i];
	}
	memset(f,0x3f,sizeof(f));
	
	for (int i = 1; i <= n; ++i)
	{
		ss[i][i]=i;
		f[i][i] = 0;
		sum[i]=sum[i-1]+a[i];
	}

	for (int len = 2; len <= n; ++len)
	{
		for (int l = 1; l <= n-len+1; ++l)
		{
			int r = l+len-1;
			for (int k = ss[l][r-1]; k <=ss[l+1][r] ; ++k)
			{
				if(f[l][r]>f[l][k]+f[k+1][r])
				{
					f[l][r] = f[l][k]+f[k+1][r];
					ss[l][r]=k;	
				}
			}	
				f[l][r]+=sum[r]-sum[l-1];
		}
	}
	printf("%d\n",f[1][n]);
	return 0;
}

 

但是当题目 N<=40000时,朴素DP不能用了。需要GarsiaWachs算法,不会证明,直接看说步骤:以下步骤和实例为抄袭

https://blog.csdn.net/acdreamers/article/details/18043897

设序列是stone[],从左往右,找一个满足stone[k-1] <= stone[k+1]的k,找到后合并stone[k]和stone[k-1],再从当前位置开始向左找最大的j,使其满足stone[j] > stone[k]+stone[k-1],插到j的后面就行。一直重复,直到只剩下一堆石子就可以了。在这个过程中,可以假设stone[-1]和stone[n]是正无穷的。

举个例子:

186 64 35 32 103

因为35<103,所以最小的k是3,我们先把35和32删除,得到他们的和67,并向前寻找一个第一个超过67的数,把67插入到他后面,得到:186 67 64 103,现在由5个数变为4个数了,继续:186 131 103,现在k=2(别忘了,设A[-1]和A[n]等于正无穷大)234 186,最后得到420。最后的答案呢?就是各次合并的重量之和,即420+234+131+67=852。

 

基本思想是通过树的最优性得到一个节点间深度的约束,之后证明操作一次之后的解可以和原来的解一一对应,并保证节点移动之后他所在的深度不会改变。具体实现这个算法需要一点技巧,精髓在于不停快速寻找最小的k,即维护一个“2-递减序列”朴素的实现的时间复杂度是O(n*n),但可以用一个平衡树来优化,使得最终复杂度为O(nlogn)。

 

输入输出格式

输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式:

输出共2行,第1行为最小得分,第2行为最大得分.

输入输出样例

输入样例#1: 复制

4
4 5 9 4

输出样例#1: 复制

43
54

环形的石子合并,想象一排有2N堆的石子,在这个2N的链条上,使其[i~i+N-1]合并成一个顶点就可以了;转移方程如下

 

 

#include 
#include 
#include 
#include 
#include 
#include 

#define frein freopen("D:\\cprogram\\acmprogram\\input", "r", stdin)
#define freout freopen("D:\\cprogram\\acmprogram\\ouput", "w", stdout)
#define MAXN 1005
#define MAXM 500005
#define INF 1000000000
using namespace std;
int f[309][309];
int f2[309][309];
int a[309],sum[309];

int main(int argc, char const *argv[])
{
	int n;
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d",&a[i]);
		a[i+n] = a[i];
	}
	memset(f,0x3f,sizeof(f));
	memset(f2,0,sizeof(f2));
	
	for (int i = 1; i <= 2*n; ++i)
	{
		f2[i][i] = 0;
		f[i][i] = 0;
		sum[i]=sum[i-1]+a[i];
	}

	for (int len = 2; len <= n; ++len)
	{
		for (int l = 1; l <= 2*n-len+1; ++l)
		{
			int r = l+len-1;
			for (int k = l; k < r ; ++k)
			{
				f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]);
				f2[l][r] = max(f2[l][r],f2[l][k]+f2[k+1][r]);
			}	
				f[l][r]+=sum[r]-sum[l-1];
				f2[l][r]+=sum[r]-sum[l-1];
		}
	}
	int ansmin = 0x3f3f3f3f,ansmax = 0;
	for (int i = 1; i <= n; ++i)
	{
		ansmax = max(f2[i][i+n-1],ansmax);
		ansmin = min( f[i][i+n-1],ansmin);
	}
	printf("%d\n%d\n",ansmin,ansmax);
	return 0;
}

 

你可能感兴趣的:(树形动态规划,石子合并极其变种问题)