题解:CF1286F Harry The Potter

题意:给出一个序列 \(a\),可以做一下两种操作:

  1. 给一个数减去 \(x\)

  2. 给一个数减去 \(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;
}
posted @ 2026-01-06 23:26  LUlululu1616  阅读(9)  评论(3)    收藏  举报