题意
定义函数
给你 个正整数 和一个 ,每次可以选择两个 删去,并把 加入
问最后剩下的一个数是否是 ,如果是请输出选数的方案
题解
注意到 范围可以考虑状压 ,设 为全集
设 表示选数集合为 和最后的数为 的情况是否存在
由于 是从小到大枚举的
那么就有
特别的如果 ,那么
这个转移可以使用可以使用 优化
那么就有( 在状压里表示就是 )
f[s]|=f[s-(1<<i)]<<a[i]
最后只要判断 是否为 即可
这部分的时间复杂度为
考虑如何使用上述 的值来打印方案
限定一个状态
如果可以就从 转移过来(其中 )
否则就从 转移过来(其中 )
因为 只能从 转移过来,即一定存在一种方案构造出
而 不能保证一定存在一种选数方案
而题目又说输出任意一种方案即可,所以可以稳妥一点地选择
那么又如何限制合并的顺序呢?
考虑 的是由状态 转移过来的(其中 )
如果存在 使得 那么就一定要先将 全部合并完了在与 合并再得出 的状态,也就是 的里数要比 优先选择(理由和上面类似)
那么可以每次从 到 时给 里的数的优先级全加上
最后优先级越高的越先合并,合并完得到的新数的优先级和取出来的两个数中的优先级最高的数相同
但是如果新数能被 整除,那么优先级就要相应地下降
这部分的时间复杂度是
总时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=2005,M=1<<16;
using arr=int[M];
#define Pii pair<int,int>
bitset<N>f[M];
priority_queue<Pii >q;
int n,k,T,Sum,a[20],Rank[20];
void dfs(int S,int p){
if(!S)return;
int r=0;
for(;p*k<=Sum&&f[S][p*k];p*=k,++r);
for(int i=0;i<n;++i)
if(S&(1<<i))
Rank[i]+=r;
for(int i=0;i<n;++i)
if(S&(1<<i)&&p>=a[i]&&f[S-(1<<i)][p-a[i]])
return dfs(S-(1<<i),p-a[i]);
}
int main(){
scanf("%d%d",&n,&k);
T=(1<<n)-1;f[0][0]=1;
for(int i=0;i<n;++i)
scanf("%d",a+i),Sum+=a[i];
for(int s=1;s<=T;++s){
for(int i=0;i<n;++i)if(s&(1<<i))
f[s]|=f[s-(1<<i)]<<a[i];
for(int j=Sum/k;j;--j)
if(f[s][j*k])
f[s][j]=1;
}
if(!f[T][1])return puts("NO"),0;
puts("YES");
dfs(T,1);
for(int i=0;i<n;++i)
q.push(make_pair(Rank[i],a[i]));
#define sum second
#define rank first
while(q.size()>1){
Pii u=q.top();q.pop();
Pii v=q.top();q.pop();
printf("%d %d\n",u.sum,v.sum);
for(u.sum+=v.sum;u.sum%k==0;u.sum/=k)
--u.rank;
q.push(u);
}
#undef sum
#undef rank
return 0;
}