题目:
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)