题解:CF1286F Harry The Potter
题意:给出一个序列 \(a\),可以做一下两种操作:
-
给一个数减去 \(x\)。
-
给一个数减去 \(x\),然后给另一个数减去 \(x+1\)。
这里 \(x\) 可以是负数。问最小操作次数。\(n\le 20\),时限 9s。
做法:
\(n\) 很小,可以思考指数级做法。
假设第二种操作两个点间连一条边,如果连出来一个环,那么我们完全可以换成第一种操作,这样不劣。所以一定是连出来一颗树,代价减一。
那么考虑一棵大小为 \(k\) 的树怎么样合法,我们考虑偶数层和奇数层都同时减去一个 \(x\),只是多一个 \(+1\),那么我们可以对他们两端任意一个加一,所以就是把这一个集合分成两个集合,要求差不超过 \(k\) 且和 \(k\) 同奇偶。考虑怎么判断对于一个集合是否可以找到一个树,记为 \(f_s\)。
我们可以解出来一个区间 \([l,r]\),只要存在一个子集和在这个范围内即可,我们考虑计数,差分一下就等于找 \(\le x\) 的子集有多少个。
计算子集中 \(\le x\) 的很麻烦,我们考虑 \(\le x\) 的里面有多少个是子集。把所有集合和询问的集合按子集和排序,然后就是一个点加求子集和,这个可以用经典的对高低位拆开维护做到 \(O(2^\frac{n}{2}3^\frac{n}{2} = 6^{\frac{n}{2}})\)。
然后最后我们记 \(dp_{s}\) 代表集合 \(s\) 最多能节省多少代价,\(dp_{s} = \max\limits_{t\in s} dp_t + f_{s\oplus t}\)。直接做是 \(3^n\),因为时间开的很久所以可以直接过,但是我们可以给一个更牛一点的做法。考虑这个东西其实是类似于一个划分的东西,记 \(F\) 是 \(f\) 的集合幂级数,有点类似于 集合幂级数 exp,但是直接 exp 发现没啥意义,但是如果是 \(F^k\),其中存在一个元素非零,那么就意味着存在一组划分有贡献,那么就可以节省 \(k\) 的代价,直接做是 \(O(2^nn^3)\),发现答案可以二分,直接二分得到 \(O(2^nn^2\log n)\),但是好像说由于有点常数所以跑的不是特别快。
有很多小情况要注意一下。
偷懒写了 \(3^n\) 的:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 21;
int n, v[maxn], lwb[1 << maxn], s[1 << maxn], cnt[1 << maxn];
struct node {
int p, val, coef;
friend bool operator<(node x, node y) {
return x.val < y.val;
}
} ;
vector<node> ad, qr;
int f[1 << maxn], a[(1 << maxn / 2)][(1 << maxn / 2)], dp[1 << maxn];
int query(int x) {
int ans = 0, lb = (x & 1023), rb = ((x >> 10) & 1023);
for (int s = rb; ; s = (s - 1) & rb) {
ans += a[s][lb];
if(!s)
break;
}
return ans;
}
void add(int x) {
int ans = 0, lb = (x & 1023), rb = ((x >> 10) & 1023);
for (int s = lb; ; s = (s + 1) | lb) {
a[rb][s]++;
if(s == (1 << 10) - 1)
break;
}
}
signed main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> v[i];
for (int i = 1; i < (1 << n); i++) {
lwb[i] = lwb[i >> 1] + 1;
if(i & 1)
lwb[i] = 0;
}
for (int i = 1; i < (1 << n); i++)
s[i] = s[i ^ (1 << lwb[i])] + v[lwb[i]], cnt[i] = cnt[i >> 1] + (i & 1);
for (int i = 1; i < (1 << n); i++) {
if(cnt[i] <= 1) {
f[i] = (v[lwb[i]] == 0);
continue;
}
if((s[i] % 2 + 2) % 2 != (cnt[i] - 1) % 2)
continue;
// cout << i << " " << (s[i] + cnt[i] - 1) / 2 << " " << (s[i] - cnt[i] + 1) / 2 << endl;
if((s[i] - cnt[i] + 1) / 2 <= s[i] && s[i] <= (s[i] + cnt[i] - 1) / 2)
f[i] = -1;
qr.push_back(node{i, (s[i] + cnt[i] - 1) / 2, 1});
qr.push_back(node{i, (s[i] - cnt[i] + 1) / 2 - 1, -1});
}
for (int i = 1; i < (1 << n); i++)
ad.push_back(node{i, s[i], 1});
sort(ad.begin(), ad.end()), sort(qr.begin(), qr.end());
int p = 0;
for (int i = 0; i < ad.size(); i++) {
while(p < qr.size() && qr[p].val < ad[i].val)
f[qr[p].p] += qr[p].coef * query(qr[p].p), p++;
add(ad[i].p);
}
for (int i = 0; i < (1 << n); i++) {
f[i] = (f[i] > 0 ? 1 : 0);
// if(f[i])
// cout << i << endl;
}
for (int i = 1; i < (1 << n); i++)
for (int s = i; s; s = (s - 1) & i)
dp[i] = max(dp[i], dp[i ^ s] + f[s]);
cout << n - dp[(1 << n) - 1] << endl;
return 0;
}

浙公网安备 33010602011771号