"ZUMA"(COCI#2009-2010#contest 5)

(POJ2915类似)

题意

给定一个序列A长度为N(由1到100的整数构成),再给定一个数M,你可以在序列的最前,最后,或中间任意位置插入任意的数。当你插入数后,如果连续的数达到或超过M,则可以让它们一起消失,两边的数会自动连接在一起。现在要你求出最少需要插入多少个数才能使所有的序列消失。(emmm.....就是小时候玩的祖玛)

分析

首先应该可以想到一个二维的区间dp,f(i,j),表示要使区间(i,j)数消失所需要的最小次数,但是这样还不够,因为我们没法用这个状态表示我们是如何插入数的,所以我们还得在修改一下。

我们在插入数的时候,肯定只会加入与前一个数或后一个数相同的的数,否则加入并不是更优的,所以我们可以得到状态f(i,j,k),表示的是在区间(i,j)前加上一段长度为k,且数字和Ai相同的序列,要消除这整个序列所需要的最小操作次数。

例如对于A={1, 3, 3, 3, 2, 3, 1, 1, 1, 3},f[1][5][3]表示消除序列B={1,1, 1, 1, 3, 3, 3, 2}(红色即为加上的序列)所需要的最小次数。

那么对于状态f(i,j,k)可得以下转移:

1.普通情况:

我们可以在当前状态继续在前面加一个与Ai相同的数即:f(i,j,k)=f(i,j,k+1)+1。

注意(1):"+1"是由于添加一个数就是一次操作。

(2):此时前面一共有k+1个与Ai相同的数。

2.当此时k=M-1:

那么我们可以消除前M个啦,且i前进一位,即:f(i,j,k)=f(i+1,j,0)。

注意:此状态与情况1相对

3.最复杂的情况:

如果在区间(i,j)中在Ai后面有和Ai相同的的数,根据规则,我们可以先消掉他俩之间的数,再消掉剩下合并的一段,即:

f(i,j,k)=f(i+1,mid-1,0)+f(mid,j,k+1)-----当Ai=Amid时,枚举即可。

最后附上丑陋的代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int f[101][101][101];
int k,n,d[101];
int dp(int l,int r,int addn)
{
	if(f[l][r][addn]!=-1) return f[l][r][addn];
	if(l>r) return 0;
	if(l==r) return k-addn-1;
	int res=2147483647;
	if(addn==k-1) res=min(res,dp(l+1,r,0));
	if(addn<k-1) res=min(res,dp(l,r,addn+1)+1);
	for(int j=l+1;j<=r;j++)
	if(d[l]==d[j])
	res=min(res,dp(l+1,j-1,0)+dp(j,r,min(k-1,addn+1)));
	f[l][r][addn]=res;
	return res;
}
int main()
{
	freopen("ZUMA.in","r",stdin);
	freopen("ZUMA.out","w",stdout);
	memset(f,-1,sizeof(f));
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
	scanf("%d",&d[i]);
	printf("%d",dp(1,n,0));
	fclose(stdin);
	fclose(stdout);
	return 0;
}
新人一枚....

请大佬指教


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是祖玛游戏的代码,使用C++实现: ```c++ #include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; int n, ans; // n表示小球的个数,ans表示最少需要几次操作 char s[200]; int find(int l, int r) // 找到最长的一段相同颜色的小球 { int i = l, j = r; while (i < j) { while (i < j && s[i] == s[i+1]) i++; while (i < j && s[j] == s[j-1]) j--; if (i < j && s[i] == s[j]) i++, j--; else break; } return i; } void solve() { ans = n; for (int i = 0; i < n; i++) { int j = find(i, n-1); if (j < n-1) // 如果有可以消除的小球 { int k; for (k = j+1; k < n; k++) if (s[k] != s[j]) break; // 找到第一个与s[j]颜色不同的小球 for (int l = k-j; l >= 0; l--) { // 枚举将s[j]和后面的l个小球消除 int start = max(0, i-l); // left int end = min(n-1, j+1); // right for (int r = start; r <= end; r++) { int t = find(start, end); char c = s[t]; int len = t - r + 1; for (int p = t+1; p < n; p++) s[p-len] = s[p]; // 将后面的小球向前移len个位置 n -= len; ans = min(ans, l+1 + solve()); // 递归 n += len; for (int p = n-1; p >= t+1-len; p--) s[p+len] = s[p]; // 将小球复原 for (int p = t-l+1; p <= t; p++) s[p] = c; } } break; } } } int main() { while (cin >> s && s[0] != 'E') { n = strlen(s); solve(); cout << ans << endl; } return 0; } ``` 该代码使用了分治法,对于每个区间,找到其中最长的一段相同颜色的小球,然后枚举将这一段小球和之后的小球一起消除的情况,递归求解。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值