CF1286F Harry the Potter
CF1286F Harry the Potter
给定 \(n\) 个数,你的目标是将所有数变成 \(0\)
可以执行以下两种操作:
- 选定 \(a_i\),将其减去 \(x\)
- 选定 \(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 ;
}