CF1225G To Make 1 题解
题目描述
给定 \(n\) 个数 \(a_i\) ,保证 \(k\not\mid a_i\) 。
记 \(f(x)\) 为 \(x\) 除尽所有 \(k\) 的因子以后剩下的部分,每次你可以选择两个数 \(x,y\) ,然后将它们合并为一个数 \(f(x+y)\) 。
问能否将所有数合并成一个数 \(1\) ,并输出方案。
数据范围
- \(2\le n\le 16,2\le k\le 2000\)。
- \(a_i\ge 1,\sum a_i\le 2000\) 。
时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
先给结论:合法当且仅当存在 \(b_i\ge 0\) ,满足 \(\sum_{i=1}^na_i\cdot k^{-b_i}=1\) 。
证明:
必要性 \((\Rightarrow)\) 显然。
充分性 \((\Leftarrow)\) :对 \(n\) 归纳,考虑 \(x=\max b_i\) ,显然 \(x\) 至少在 \(b\) 中出现两次,将这两个数合并后转化为 \(n-1\) 的问题。
构造方案用优先队列维护二元组 \((a,b)\) 即可,每次挑 \(b\) 最大的两项合并。
接下来的目标是找到一组满足要求的 \(b_i\) 。
\(dp_{s,j}\) 表示用了集合 \(s\) 中的数,能否通过选择 \(b\) 使得和为 \(j\) 。
转移分为两种:
- 使用一个数 \(i\) ,转移方程
dp[s][j]|=dp[s^(1<<i)][j-a[i]]。 - 给所有 \(b\) 增加 \(1\),转移方程
dp[s][j]|=dp[s][j*k]。
第二维需要开到 \(\sum_{i=1}^na_i\) ,直接做时间复杂度 \(\mathcal O(n2^n\sum a_i)\approx 2\cdot 10^9\) ,无法通过。
bitset 优化第一类转移,时间复杂度降至 \(\mathcal O(\frac{n2^n\sum a_i}w+\frac{2^n\sum a_i}k)\) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=2005;
int k,n,sum;
int a[maxn],b[maxn];
bitset<maxn> dp[1<<16];
priority_queue<pii> q;
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d",&a[i]),sum+=a[i];
dp[0][0]=1;
for(int s=1;s<1<<n;s++)
{
for(int i=0;i<n;i++) if(s>>i&1) dp[s]|=dp[s^(1<<i)]<<a[i];
for(int j=sum/k;j>=1;j--) if(dp[s][j*k]) dp[s][j]=true;
}
if(!dp[(1<<n)-1][1]) printf("NO\n"),exit(0);
printf("YES\n");
for(int s=(1<<n)-1,j=1;s;)
{
int flg=0;
for(int i=0;i<n;i++)
if((s>>i&1)&&j>=a[i]&&dp[s^(1<<i)][j-a[i]])
{
flg=1,s^=1<<i,j-=a[i];
break;
}
if(flg) continue;
j*=k,assert(j<=sum&&dp[s][j]);
for(int i=0;i<n;i++) if(s>>i&1) b[i]++;
}
for(int i=0;i<n;i++) q.push(mp(b[i],a[i]));
while(q.size()>=2)
{
auto u=q.top();
q.pop();
auto v=q.top();
q.pop();
printf("%d %d\n",u.se,v.se);
assert(u.fi==v.fi),u.se+=v.se;
while(u.se%k==0) u.se/=k,u.fi--;
q.push(u);
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16536894.html
浙公网安备 33010602011771号