CF1286F Harry the Potter

CF1286F Harry the Potter

给定 \(n\) 个数,你的目标是将所有数变成 \(0\)

可以执行以下两种操作:

  1. 选定 \(a_i\),将其减去 \(x\)
  2. 选定 \(a_i,a_j\),给 \(a_i\) 减去 \(x\)\(a_j\) 减去 \(x+1\)

\(n\le 20,a_i\in [-10^{15},10^{15}]\)

time limit : 9s,1000MB

Solution

不难发现执行第一类操作一定是把 \(a_i\) 变成 \(0\)

考虑执行第二类操作,每次操作 \(u,v\) 那么连接 \(u\to v\)

不难发现我们一定没有环,如果存在环那么操作次数等于点数不如直接全用 \(1\) 类操作。

所以如果某个点集可以在不使用 \(2\) 操作的情况下消去等价于构成了一棵树。

不难发现我们可以从叶子节点处开始删点,会发现仅有根节点可能会无法删去,同时可以发现非叶节点对根的贡献仅与深度有关。

对于奇深度,贡献为 \(+a_x\pm 1\)

对于偶深度,贡献为 \(-a_x\pm 1\)

同时操作完后根节点的权值应该 \(0\),所以我们不妨将根节点 \(+1\) 的权值也统计进来,不难发现问题变成奇深度权值减去偶深度权值和加上 \(k-2x\)\(0\),其中 \(k\)\(|S|-1\)\(x\)\(0\sim |S|-1\)

暴力只能做到 \(\mathcal O(3^n)\)

考虑条件等价于什么,不难发现即 \(w_1+w_2=w,w_1-w_2\in [0,|S|-1]\) 且奇偶性与 \(|S|-1\) 相同。

所以 \(2w_1\in [w,|S|-1+w]\) 且奇偶性与 \(|S|-1+w\) 相同。

所以可以直接考虑折半搜索,常规操作直接折半搜索+双指针可以得到一个 \(\mathcal O(2^{\frac{|S|}{2}})\) 的做法。

可以通过双指针维护奇数 \(+\) 偶数两个单调的数组做到 \(\mathcal O(2^{\frac{|S|}{2}}|S|)\)

总体复杂度为 \(\mathcal O(n\sum \binom{n}{i}(\sqrt{2})^{i}=(1+\sqrt{2})^nn)\)

  • 这里我写的是哈希,本来可以双指针,仁者见仁吧。

接下来考虑本题怎么做。

不难发现转移的过程也可以通过子集 dp 来暴力处理,复杂度是 \(\mathcal O(3^n)\) 的。

由于要取 \(\max\),我们的常规手段似乎难以优化,但是实际上不难发现我们的目标是最大化全集 \(S\) 拆分出来的合法子集数量。

于是问题变成了一个重复覆盖题,可以考虑二分答案,同时通过子集卷积来优化,复杂度为 \(\mathcal O(2^n\times n^2\log n)\)

实际上对暴力做法进行减枝是可以跑得更快的。

不难注意到假设一个状态 \(S\) 由若干个子集组成,那么我们可以考虑枚举其编号最大的子集 \(T\),然后由 \(T\) 去更新 \(S\) 的答案。

所以当且仅当一个状态 \(T'\) 满足其无法由其他子集构成的时候,我们就去更新答案。

复杂度不确定,但实际效率非常好。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int N = 20 + 5 ; 
int n, a[N], m, b[N], dp[(1 << 20) + 5] ; 
int flag, c[N], num, K, cnt, st[(1 << 12)], top ; 
struct hashmap {
	static const int maxn = 2192 , P = 999983 ;
	struct node {
		int next, key, val ;
	} e[maxn] ;
	int head[P + 100], cnt ;
	inline void ins(int x, int k) {
		int hsh = (x % P + P) % P ;
		for (int i = head[hsh]; i; i = e[i].next)
			if (e[i].key == x) return void(e[i].val += k) ;
		e[++ cnt] = (node){head[hsh], x, 1}, head[hsh] = cnt ;
	}
	inline bool qry(int x) {
		int hsh = (x % P + P) % P ; 
		for ( int i = head[hsh]; i; i = e[i].next)
			if( e[i].key == x && e[i].val > 0 ) return 1 ;
		return 0 ; 
	}
	inline void del(int x) {
		head[(x % P + P) % P] = 0 ; 
	}
	inline void clear() { 
		rep( i, 1, cnt ) e[i].next = e[i].key = e[i].val = 0 ;
		cnt = 0 ; 
	}
} Hsh ; 
void Dfs1(int x, int ed, int S) {
	if( x == ed + 1 ) {
		return Hsh.ins(2 * S, 1), st[++ top] = S, void() ; 
	}
	Dfs1(x + 1, ed, S), Dfs1(x + 1, ed, S + c[x]) ; 
}  
void Dfs2(int x, int ed, int fl, int S) {
	if( x == ed + 1 ) { 
		if( !fl ) Hsh.ins(0, -1) ;  
		for( re int j = K + cnt - 1; j >= K; j -= 2 ) 
			if( Hsh.qry(j - 2 * S) ) flag = 1 ; 
		if( !fl ) Hsh.ins(0, 1) ; 
		return ; 
	}
	Dfs2(x + 1, ed, fl, S), Dfs2(x + 1, ed, 1, S + c[x]) ; 
} 
bool check(int x) {
	int S = 0 ; num = 0, flag = 0, cnt = 0, top = 0 ; 
	rep( i, 1, n ) if( (x >> (i - 1)) & 1 ) c[++ num] = a[i], S += a[i], ++ cnt ; 
	if( num == 1 || ((S & 1) != ((cnt ^ 1) & 1))) return 0 ; 
	Dfs1(1, num / 2, 0) ; 
	K = S, Dfs2(num / 2 + 1, num, 0, 0) ; 
	rep( i, 1, top ) Hsh.del(2 * st[i]) ; Hsh.clear() ;
	return flag ; 
}
signed main()
{
	m = gi() ; 
	rep( i, 1, m ) {
		b[i] = gi() ; 
		if( b[i] != 0 ) a[++ n] = b[i] ; 
	}
	if( n == 0 ) { puts("0") ; exit(0) ; }
	int lim = (1 << n) - 1 ; 
	rep( i, 1, lim ) if( (dp[i] == 0) && check(i) ) {
		dp[i] = 1 ; 
		for(re int k = i; k <= lim; k = (k + 1) | i ) dp[k] = max( dp[k], 1 + dp[i ^ k] ) ;
	}
	dp[lim] = max( dp[lim], 0ll ) ;
	printf("%lld\n", n - dp[lim] ) ;
	return 0 ;
}
posted @ 2020-09-13 21:25  Soulist  阅读(181)  评论(0)    收藏  举报