ACM DP 石子合并问题

滴,集训第二十一天打卡。

可能是对组队不太满意,都不大高兴做新的训练...

所以最近一直在磨DP,翻一下博客,发现最近都是DP啊...

这个石子合并的,我做的训练数据n是40000直线型的,一开始没注意,先做了环形,再换直线,然后发现朴素DP不能用了...

再看的GarsiaWachs算法,一上午啊,都在这了..

 

部分参考来自http://blog.youkuaiyun.com/acdreamers/article/details/18039073

 

石子合并有三种题型。

 

(1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

 

分析:当然这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。本问题实际上就是哈夫曼的变形。

 

(2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

 

分析:我们熟悉矩阵连乘,知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解决。

 

设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:

 

 

最普通的算法O(n^3):

 

其中,dp[i][j]代表i到j堆的最优值,sum[i]代表第1堆到第i堆的数目总和。

 

#include <stdio.h>
#include <algorithm>
using namespace std;
int dp[205][205],sum[205],a[205];
int main()
{
    int n,i,j,k,l,s;
    while(scanf("%d",&n)!=EOF)
    {
    	s=0;
       	for(i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			s+=a[i];
			sum[i]=s;
		}
   		//memset(dp,0,sizeof(dp));
   		for(i=1;i<=n;i++)
		dp[i][i]=0;
   		for(l=1;l<n;l++)
   		{
		 	for(i=1;i<=n-l;i++)
	 		{
				j=i+l;
				dp[i][j]=1000000000;
				for(k=i;k<=j;k++)
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
	            dp[i][j]+=sum[j]-sum[i-1];
 			}  	
  		}
		printf("%d\n",dp[1][n]);
    }
}

 

考虑四边形不等式优化,接近O(n^2):

 

原状态转移方程中的k的枚举范围便可以从原来的(i~j-1)变为(s[i,j-1]~s[i+1,j])。

 

#include <stdio.h>
#include <algorithm>
using namespace std;
int dp[105][105],sum[105],a[105],ss[105][105];
int main()
{
    int n,i,j,k,l,s;
    while(scanf("%d",&n)!=EOF)
    {
   		s=0;
	   	for(i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			s+=a[i];
			sum[i]=s;
		}
		//memset(dp,0,sizeof(dp));
		for(i=1;i<=n;i++)
		dp[i][i]=0,ss[i][i]=i;
		for(l=1;l<n;l++)
		{
		 	for(i=1;i<=n-l;i++)
	 		{
				j=i+l;
				dp[i][j]=1000000000;
				for(k=ss[i][j-1];k<=ss[i+1][j];k++)
	 			{
			 		if(dp[i][j]>dp[i][k]+dp[k+1][j])
					{
	                    dp[i][j]=dp[i][k]+dp[k+1][j];
	                    ss[i][j]=k;
	                }
			 	}
	            dp[i][j]+=sum[j]-sum[i-1];
			}  	
		}
		printf("%d\n",dp[1][n]);
    }	
}

 

HYSBZ-3229 石子合并(GarsiaWachs算法)

 

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

Input

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

Output

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

Sample Input

4

1

1

1

1

Sample Output

8

HINT

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

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

思路:区间DP不能做了,范围40000开二维太大了。

 

#include <stdio.h>
int stone[40005],ans,t;
void combine(int k)
{
	int tmp,i,j,d;
	tmp=stone[k-1]+stone[k];
	ans+=tmp;
	t--;
	for(i=k;i<t;i++) 
	stone[i]=stone[i+1];
	for(j=k-1;stone[j-1]<tmp;j--)
	stone[j]=stone[j-1];
	stone[j]=tmp;
	while(j>=2&&stone[j]>=stone[j-2])
	{
		d=t-j;
		combine(j-1);
		j=t-d;
	}
}
int main()
{
	int n,i,j;
	scanf("%d",&n);
	stone[0]=0x3f3f3f3f; 
	stone[n+1]=0x3f3f3f3f-1;
	for(i=1;i<=n;i++)
	scanf("%d",&stone[i]);
	ans=0;t=3;
	for(i=3;i<=n+1;i++)
	{
		stone[t++]=stone[i];
		while(stone[t-3]<=stone[t-1])
		combine(t-2);
	}
	while(t>3) 
	combine(t-1);
	printf("%d\n",ans);
	return 0;
}

 

(3)问题(2)的是在石子排列是直线情况下的解法,如果把石子改为环形排列,又怎么做呢?

 

 

分析:状态转移方程为:

 

 

 

 

其中有:

 

 

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[1005][1005];
int main()
{
	int n,i,j,a[1001],sum[1005],s=0,k,l;
	scanf("%d",&n);
	sum[0]=0;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s+=a[i];
		sum[i]=s;
	}
	memset(dp,0,sizeof(dp));
	for(l=2;l<=n;l++)//归并的石子长度
	{
		for(i=1;i<=n-l+1;i++)//i为起点,j为终点
		{
			j=i+l-1;
			dp[i][j]=1000000000;
			for(k=i;k<=j;k++)
			dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
		}
	}
	printf("%d\n",dp[1][n]);
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值