peiwenjun's blog 没有知识的荒原

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;
}

posted on 2022-07-31 12:47  peiwenjun  阅读(16)  评论(0)    收藏  举报

导航