HDU 5900 QSC and Master (区间DP)

3 篇文章 0 订阅
 题目链接   http://acm.hdu.edu.cn/showproblem.php?pid=5900

题意:给出序列Ai.key和Ai.value,若当前相邻的两个数Ai.key和Ai+1.key的最大公约数大于1,则可以把这两个数消去,同时消去Ai.value和Ai+1.value,每次消去得到的分数为Ai和Ai+1的value值,问最大可能得分。

注意:当Ai和Ai+1被消去后,Ai-1和Ai+2成为了新的相邻的数。若符合条件则可以继续消去。

思路:很明显是区间DP,但是我比赛中如何也A不了。原因有两个:1.没有注意到位运算的低优先级。2.没有把所有情况考虑清楚。

         设f[i][j]为从第i个数到第j个数所能得到的最大得分,j-i+1从2开始依次枚举,最后枚举到f[1][n](考虑DP的无后效性)

         分类讨论即可。先考虑不合并的情况,则f[i][j]被f[i][k]+f[k + 1][j]依次更新。

          再考虑合并的情况f[i][j]可以被f[i][k] + a[k + 1] + a[k + 2] + f[k +3][j]更新。

          特殊情况讨论:当a[i + 1]到a[j - 1]中所有的数都可以被消去,则a[i]和a[j]作为相邻的数也可以被消去。这种情况的讨论非常重要!!!

      代码送上

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <functional>

using namespace std;

#define REP(i,n)		for(int i(0); i <  (n); ++i)
#define rep(i,a,b)		for(int i(a); i <= (b); ++i)
#define dec(i,a,b)		for(int i(a); i >= (b); --i)
#define for_edge(i,x)		for(int i = H[x]; i; i = X[i])

const int N	=	100000		+	10;
const int M	=	10000		+	10;
const int Q	=	1000		+	10;
const int A	=	30		+	1;

typedef long long LL;

LL f[Q][Q];
bool c[Q][Q];
bool ret[Q][Q];
int T;
int n;
LL a[Q], b[Q];
LL gcd(LL a, LL b){ return b == 0 ? a : gcd(b, a % b); }


int main(){
	scanf("%d", &T);
	while (T--){
		scanf("%d", &n);
		memset(a, 0, sizeof a);
		memset(b, 0, sizeof b);
		memset(c, false, sizeof c);
		memset(ret, false, sizeof ret);
		memset(f, 0, sizeof f);
		rep(i, 1, n) scanf("%lld", a + i);
		rep(i, 1, n) scanf("%lld", b + i);
		rep(i, 1, n - 1) rep(j, i + 1, n) if (gcd(a[i], a[j]) != 1) c[i][j] = true;
		rep(i, 1, n - 1) if (gcd(a[i], a[i + 1]) > 1) ret[i][i + 1] = true;
		for (int len = 4; len <= n; len += 2){
			rep(i, 1, n - len + 1){
				int j = i + len - 1;
				bool flag = false;
				if (c[i][i + 1] && ret[i + 2][j]) flag = true;
				if (c[j - 1][j] && ret[i][j - 2]) flag = true;
				if (c[i][j] && ret[i + 1][j - 1]) flag = true;
				for (int k = i + 2; k <= j - 3; k += 2) if (ret[k][k + 1] && ret[i][k - 1] && ret[k + 2][j]){ flag = true; break;}
				if (flag) ret[i][j] = true;
			}
		}
		
		rep(i, 1, n - 1) if (c[i][i + 1]) f[i][i + 1] = b[i] + b[i + 1];
		rep(i, 1, n - 2) f[i][i + 2] = max(f[i][i + 1], f[i + 1][i + 2]);
		rep(len, 4, n){
			rep(i, 1, n - len + 1){
				int j = i + len - 1;
				rep(k, i, j) f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]);
				if (len % 2 == 0){ if (ret[i + 1][j - 1] && c[i][j]) f[i][j] = max(f[i][j], b[i] + b[j] + f[i + 1][j - 1]); }
				f[i][j] = max(f[i][j], f[i + 1][j]);
				f[i][j] = max(f[i][j], f[i][j - 1]);
				if (c[i][i + 1]) f[i][j] = max(f[i][i + 1] + f[i + 2][j], f[i][j]);
				if (c[j - 1][j]) f[i][j] = max(f[j - 1][j] + f[i][j - 2], f[i][j]);
				rep(k, i + 1, j - 2) if (c[k][k + 1]) f[i][j] = max(f[i][j], f[i][k - 1] + f[k][k + 1] + f[k + 2][j]);
			}
		}
		printf("%lld\n", f[1][n]);
		
		
	}
	
	
	return 0;
	
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值