CF1225G To Make 1 题解
目录
题目描述
给定 个数 ,保证 。
记 为 除尽所有 的因子以后剩下的部分,每次你可以选择两个数 ,然后将它们合并为一个数 。
问能否将所有数合并成一个数 ,并输出方案。
数据范围
- 。
- 。
时间限制 ,空间限制 。
分析
先给结论:合法当且仅当存在 ,满足 。
证明:
必要性 显然。
充分性 :对 归纳,考虑 ,显然 至少在 中出现两次,将这两个数合并后转化为 的问题。
构造方案用优先队列维护二元组 即可,每次挑 最大的两项合并。
接下来的目标是找到一组满足要求的 。
表示用了集合 中的数,能否通过选择 使得和为 。
转移分为两种:
- 使用一个数 ,转移方程
dp[s][j]|=dp[s^(1<<i)][j-a[i]]
。 - 给所有 增加 ,转移方程
dp[s][j]|=dp[s][j*k]
。
第二维需要开到 ,直接做时间复杂度 ,无法通过。
bitset
优化第一类转移,时间复杂度降至 。
#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
标签:
动态规划-状压DP
, 算法-压位&bitset
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通