CF1225G To Make 1
CF1225G To Make 1
给定 \(n\) 个整数,你需要将所有数合成一个数。
一个合成为,删除 \(x,y\),加入 \(f(x+y)\),其中:
如果最后剩余的数可以为 \(1\),那么输出 YES
并给出方案。
否则输出 NO
\(n\le 16,k\in [2, 2000],\sum a_i\le 2000,k\not | ~a_i\)
Solution
设 \(S=\sum a_i\)
\(\mathcal O(3^nS\log S)\) 的子集 Dp 是显然的。
考虑确定一组 \(c\) 使得 \(\sum a_i\times k^{-c_i}=1\)
考虑如何检查一组 \(c\) 是合法的,那么应该有存在一种合并顺序得到 \(c\)
或者说存在一种分裂策略得到全 \(0\) 的初始局面。
可以证明对于一组合法的 \(c\) 一定存在一个策略得到他。(大胆猜测)
注意到 \(a_i\) 都是正整数,那么不难发现 \(c\) 也均为正整数。
由于不存在 \(k|a_i\),那么不妨考虑 \(c\) 最大的元素,他们一定是第一次被合并的,至少出现了两次。设其为 \(t\),那么由于 \(1=\sum a_ik^{-c_i}\rightarrow k^t=\sum a_ik^{t-c_i}\)
注意到左边被 \(k\) 整除,那么同理于右边,于是考虑 \(t=c_i\) 的元素,于是我们一定有 \(k|\sum a_i[c_i=t]\),由于 \(k\not |~a_i\),所以满足 \(c_i=t\) 的 \(a_i\) 至少有两个。
那么,注意到 \(k|\sum a_i[c_i=t]\),同时我们至少有两个 \(i,j\) 满足 \(c_i,c_j=t\),那么每次操作将他们选出来即可。
如果被 \(k\) 整除,那么相当于将 \(a_i,a_j\) 分别除以 \(k\) 然后将 \(b_i\) 缩小并规约成子问题,此时仍然满足权值和为 \(1\) 的性质。
否则相当于当前问题同时生成了 \(a_i'=a_i+a_j\) 所以问题规模仍然缩小了 \(1\)
最后会使得所有数的和为 \(1\),所以我们证明了一组合法的 \(c\) 一定是存在操作序列的。(也给出了方案)
所以我们的问题等价于,求一组 \(c\) 使得 \(\sum k^{-c_i}\times a_i=1\)
考虑状压 dp,设 \(f_{S,x}\) 表示集合 \(S\) 内的 \(a_i\) 满足存在一组 \(c\) 使得 \(\sum a_i\times k^{-c_i}=x\)
考虑单调递增的枚举 \(c_i\),我们认为加入集合的顺序表示了一个单调递减的 \(c\) 序列,这样为了确定顺序,我们需要借助子集 dp,现在我们有两个操作:
- 加入 \(a_i\),\(f_{S|2^i,x+a_i}\leftarrow f_{S,x}\)
- 将 \(c_i\) 集体增大 \(1\),\(f_{S,x/k}\leftarrow f_{S,x}\)
事实上,第一类转移复杂度为 \(\mathcal O(n2^n\cdot \sum a_i)\),第二类转移从后往前枚举可以轻易的得到一个 \(\mathcal O(2^n\sum a_i)\) 的解法。
第一类转移通过 bitset 优化,我们得到了一个时间复杂度为 \(\mathcal O(2^n\cdot \sum a(1+\frac{n}{\omega}))\),空间开销为 \(\mathcal O(\frac{2^n\sum a}{\omega})\) 的解法。
\(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
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 = (1 << 16) + 5 ;
const int S = 4047 ;
int n, k, m, flag, top, st[25], zk[25], a[25], c[25] ;
bitset<S> dp[N] ;
void Dfs(int u, int w) {
if( u == 0 && w == 0 ) return flag = 1, void();
if( flag ) return ;
if( dp[u][w * k] ) {
for(int i = 0; i < n; ++ i) if( (1 << i) & u ) ++ c[i] ;
Dfs(u, w * k) ;
}
else {
for(int i = 0; i < n; ++ i)
if( ((1 << i) & u) && a[i] <= w && dp[(1 << i) ^ u][w - a[i]] )
Dfs((1 << i) ^ u, w - a[i]) ;
}
}
signed main()
{
n = gi(), k = gi() ; int maxn = (1 << n) - 1 ;
rep( i, 1, n ) a[i - 1] = gi(), m += a[i - 1] ;
dp[0][0] = 1 ;
rep( i, 0, maxn ) {
for(re int j = m; j >= k; -- j)
if( (j % k == 0) && dp[i][j] ) dp[i][j / k] = 1 ;
for(re int j = 0; j < n; ++ j)
if( !((1 << j) & i) )
dp[i | (1 << j)] |= (dp[i] << a[j]) ;
}
if( !dp[maxn][1] ) { puts("NO") ; exit(0) ; }
puts("YES"), Dfs(maxn, 1), top = n ;
for(re int i = 1; i < n; ++ i) {
int op1 = -1, op2 = -1, mx = 0, lp = top ; top = 0 ;
for(re int j = 0; j < lp; ++ j) mx = max( mx, c[j] ) ;
for(re int j = 0; j < lp; ++ j) {
if( (c[j] == mx) && (op1 == -1) ) op1 = j ;
else if((c[j] == mx) && (op2 == -1)) op2 = j ;
else st[++ top] = a[j], zk[top] = c[j] ;
}
printf("%d %d\n", a[op1], a[op2] ) ;
int u = a[op1] + a[op2], b = mx ;
while( u % k == 0 ) u /= k, -- b ;
st[++ top] = u, zk[top] = b ;
rep( j, 1, top ) a[j - 1] = st[j], c[j - 1] = zk[j] ;
}
return 0 ;
}