POJ 1722 SUBTRACT 线性dp 个人理解

题目:

http://poj.org/problem?id=1722

https://www.acwing.com/problem/content/318/

题意:

 

内心思路:

(ai+1表示a[i+1],下面这两个有混用的情况,但是都表示同一个东西)

首先,我先想了一下这道题可不可以用粗暴的方式划分子问题,假如说有a1,a2……an需要合并成一个数的话,看看假如将a1和 a2,a3……an划分开来会有什么结果呢?

因为a1在变化中始终是与后面的数相加的,因此a1对于答案的贡献一定是正的,那么子问题就可以划分为: a2,a3……an 组成大小为t-a1的数

但是很快发现这是不行的,照着这个思路想,a2必定对答案的贡献是负,但是a2之后的所有元素正负符号就不确定了。比如a1,a2,a3 , 假如先合并a2,a3,再合并a1的话,a3对答案的贡献就是正,假如先合并a1,a2,再合并a3的话,a3对答案的贡献就是负

虽然这条路走不通,但是至少我得到了几个信息:

1.a1对答案的贡献是正的,a2对答案的贡献是负的,其他不确定

2.an对答案的贡献是正还是负取决于an被合并了多少次,假如an被合并的次数为奇数,则an为负

(注意,假如ai和ai+1合并变成ai了,那么ai-1再和ai合并的时候也算ai-1和ai+1合并了一次)

3.其实题目就等价于a1~an元素之间通过加减组合使得结果为t

 

于是就有以下猜想:

是否a3……an元素的正负号可以任意组合,也即是说当n>=3的时候,元素对结果是正贡献还是负贡献是随意的,因为总有一种合并方法可以使得该组合成立。

这种猜想是正确的,我就粗略说一下原因:

有三个元素 a[i-1],a[i],a[i+1]   (i>3

假如需要ai+1和ai同号,那么我们就应该先合并ai-1,ai ,再合并ai+1

假如需要ai+1和ai异号,那么我们就需要先合并ai和ai+1,再合并ai-1

假如需要ai+1对答案的贡献为正,那么就需要ai+1被合并偶数次,因为i>3,因此这是能做到的

同理,假如需要ai+1对答案的贡献为负,也是能做到的

简单来说,a[n+1]的正负,需要做的只是调节a[1],a[2],a[3]的合并顺序(先a[1],a[2]合并还是先a[2],a[3]合并)

而ai和ai+1是否相等,只需要调整ai和ai+1合并以及ai-1和ai合并的顺序

 

因此这题就转化为a[1]为正数,a[2]为负数,然后a[3]~a[n]正负任意组合,得到k

因为n有100的规模,暴力dfs肯定不行,注意本题数值的上界也就10000,因此可以用

dp[i][j]:表示元素i,前i个元素组合的结果为j,第i个元素对答案的贡献是正还是负(正的话是1,负的话就是-1)

先看程序,然后再讲一下如何输出合并路线

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e4 + 7; //负数变正数
int dp[107][maxn], num[107], flag[maxn];
int main() {
	int n, t;
	cin >> n >> t;
	t += 10000;//将负数全部变为正数
	for (int i = 1; i <= n; i++) {
		scanf("%d", num + i);
	}
	dp[2][10000 + num[1] - num[2]] = -1;//本题有负数,所以要+10000
	for (int i = 3; i <= n; i++) {
		for (int j = 20000 ; j; j--) {
			if (j - num[i] >= 0 && dp[i - 1][j - num[i]] != 0) {
				dp[i][j] = 1;
				//cout << "add i =" << i << " " << j << endl;
			}
			if (j + num[i] <= 20000 && dp[i - 1][j + num[i]] != 0) {
				dp[i][j] = -1;
				//cout << "sub i =" << i << " " << j << endl;
			}
		}
	}

	//确定符号
	int now_i = n, now_num = t;
	while (now_i > 1) {
		flag[now_i] = dp[now_i][now_num];
		now_num += -flag[now_i]*num[now_i];
		now_i--;
	}
	//输出路径
	int cnt = 0;
	for (int i = 2; i <= n; i++) {
		if (flag[i] > 0) {
			cout << i - cnt - 1 << endl;
			cnt++;
		}
	}
	for (int i = 2; i <= n; i++) {
		if (flag[i] < 0) {
			cout << 1<< endl;
		}
	}
	return 0;
}

输出路径:

参考我上面写过的:

假如需要ai+1和ai同号,那么我们就应该先合并ai-1,ai ,再合并ai+1

假如需要ai+1和ai异号,那么我们就需要先合并ai和ai+1,再合并ai-1

假如ai对答案的贡献为正的话,那么ai必定先和ai-1合并,因此我们先找出需要优先合并的,也就是说对答案贡献为正的

然后剩下的就是从左到右依次合并即可(也就是说一直输出1)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值