25 P10960 subtract 题解
subtract
题面
现有一个数列 \(\{a_n\}\ (2 \le n,a_i \le 100)\),你需要对它进行 \(n-1\) 次操作。其中第 \(i\) 次操作是:
- 选择一个正整数 \(t\ (1 \le t \le n-i)\);
- 计算 \(d=a_t-a_{t+1}\);
- 删除 \(a_t,a_{t+1}\) 两项;
- 在原来 \(a_t\) 的位置插入一项 \(d\)。
试构造一种操作方案,使得 \(n-1\) 次操作后数列中剩下的那个数恰好等于给定的数 \(T\ (|T| \le 10^4)\)(保证有解)。
题解
这道题个人感觉更偏向构造
先给结论对于数列中的数,最后一定要么是 + ,要么是 - ,对于 \(a_1\) 只能是 + ,对于 \(a_2\) 只能是 -
也就是最后的数列是形如 \(a_1 - a_2 \pm a_3 \pm a_4 ... \pm a_n\)
其实原来的那些操作都可以看作是在数列中加括号,比如一开始的数列是 \(a_1 - a_2 - a_3 - a_4 ... -a_n\)
我们可以通过加括号将除第一个以外的某些负号变成正号
如 \(a_1 - ((a_2 - a_3) - a_4) ... -a_n = a_1 - a_2 + a_3 + a_4 ... - a_n\)
这样的话,题目就变成了一个背包问题,每个物品可以 +/- ,最后倒推一下各个位置的符号
如果是正号,我们可以让 \(a_{i - 1} - a_i\) ,然后再让 \(a_{i - 2} - a_{i - 1}\) 这样 \(i\) 位置就变成了负号,所以我们只需将所有正号消掉,然后剩下全是负号,直接一直操作第一个元素即可
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 110, M = 1e4;
int n, m;
int a[N], c[N];
bool f[N][M * 2 + 30];
int main () {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
int base = 1e4 + 10;
f[2][base + a[1] - a[2]] = 1;
for (int i = 3; i <= n; i ++) {
//减号
for (int j = base - M; j <= base + M - a[i]; j ++) {
f[i][j] |= f[i - 1][j + a[i]];
}
//加号
for (int j = base - M + a[i]; j <= base + M; j ++) {
f[i][j] |= f[i - 1][j - a[i]];
}
}
{
int i = n, j = base + m;
while (i >= 3) {
if (f[i - 1][j + a[i]]) {
c[i] = -1;
j = j + a[i];
i --;
} else {
c[i] = 1;
j = j - a[i];
i --;
}
}
}
c[1] = 1, c[2] = -1;
int cnt = 0;
for (int i = 2; i <= n; i ++) {
if (c[i] == 1) {
printf ("%d\n", i - cnt - 1);
cnt ++;
}
}
for (int i = 1; i <= n - cnt - 1; i ++) {
cout << 1 << endl;
}
return 0;
}