• [NOI2020]制作菜品 题解


    题意分析

    给出 $n$ 个数和 $m$ 个 $k$ ,可以某些 $k$ 拆两个正整数,使得拆后的数可以拼成给出的 $n$ 个数。

    思路分析

    上面的解释是因为这样写比较方便,实际上按照题意应该是用 $n$ 个数拆分拼成 $m$ 个 $k$ 。

    观察数据范围,发现有 $mgeq n-2$ 的限制和 $mgeq n-1$ 的部分分,考虑从这里切入分析。

    先分析 $mgeq n-1$ 的情况。令 $d_{min},d_{max}$ 分别表示最小的 $d$ 和最大的 $d$ 。

    很容易想到一个贪心,若 $d_{min}<k$,用 $d_{max}$ 与其配对,让 $d_{max}$ 剩下的量尽量地大;否则就不断用 $d_{min}$ 单独做一道菜,直到它剩下的量小于 $k$ ,若还有剩余,再采用上一种策略。

    如何证明这个贪心是正确的?

    可以证明当 $mgeq n$ 时, $d_{max}geq k$ ,即与 $d_{min}$ 配对后一定有剩余,因此此时每次只会仅让 $m$ 减 $1$ ( $d_{min}geq k$ )或 $n,m$ 各减 $1$ ( $d_{min}<k$ )。

    若 $m=n-1$ ,可能会出现 $d_{min}+d_{max}leq k$ 的情况。可以证明此时 $d_{min}+d_{max}geq 0$ ,因此只会出现 $d_{min}+d_{max}=k$ 的情况,这也说明了 $m=n-1$ 的情况下一定有解。这样就会有 $n$ 减 $2$ 而 $m$ 减 $1$ ,转化为 $m=n$ 。但是根据之前的分析,当 $m=n$ 时有 $d_{max}geq k$ ,而之前的 $d_{max}< k$ 。因此这种情况当且仅当 $n=2$ 时会发生,即当前拼完之后就没有剩余的 $d$ 了,此时用剩下的两个 $d$ 拼成一个 $k$ 即可。另外,可以证明此时 $d_{min}<k$ ,即只会出现 $n,m$ 各减 $1$ 的情况。因此,只要出现 $m=n-1$ ,之后就会一直维持在这个情况,直至 $n=2$ 。

    综上,根据这个贪心策略,可以使 $mgeq n-1$ 所有情况不断向 $m=n-1$ 靠近,进入 $m=n-1$ 情况后就会保持不变,直至 $n=2$ ,此时用剩下的两个 $d$ 拼成一个 $k$ 即可,一定有解。用 set 等数据结构优化,时间复杂度 $O(mlog n)$ 。其实暴力 $O(mn)$ 也可以过

    题目中的 $sum d_i =m*k$ 是个很重要的性质,上面的“可以证明”都可以根据这个性质来证明。

    接下来分析 $m=n-2$ 的情况。

    可以想到把这种情况划分成两个独立的 $m=n-1$ 的情况进行求解,此时两部分的 $d$ 是没有重合的。会不会有重合的情况呢?若存在这种情况,即从第二个 $k$ 开始,每次配对只能多用一个 $d$ ,最后只能用 $n-1$ 个 $d$ ,显然是不可能把 $d$ 全部用完的,因此不存在这种情况。

    如何划分?

    分析这个策略,发现实质是要找到一个集合 $S$ 使得 $sum_{d_iin S} d_i=(|S|-1)*k$ , $|S|$ 表示集合 $S$ 的大小,即前面的 $n$ ,而不在这个集合内的元素就归到另一个集合。移项整理可以得到 $sum_{d_iin S} (d_i-k)=-k$ 。看到这个式子很容易想到 01 背包。用 bitset 优化,时间复杂度 $O(frac{n^2k}{omega})$ 。然后按照贪心策略分别求解,总的时间复杂度是 $O(frac{n^2k}{omega}+nlogn)$ 。注意,由于可能会出现负数,因此要将所有数处理为正数再进行 DP 。

    综上,若 $mgeq n-1$ ,直接按照贪心策略进行求解;否则,用 01 背包将所有的 $d$ 划分成满足 $sum_{d_iin S} (d_i-k)=-k$ 的两个集合,再按照贪心策略分别求解,此时一定有解。若找不到满足条件的集合,则无解。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<bitset>
    #include<set>
    using namespace std;
    const int N=600,M=2e6+5e5;
    int T,n,m,k;
    int d[N],v[N];
    bool pd[N];
    multiset<pair<int,int> > s;//维护 d_min 和 d_max
    void solve()
    {
        while(s.size())
        {
            multiset<pair<int,int> >::iterator it=s.begin();
            int minv=(*it).first,minb=(*it).second;
            s.erase(it);//取 d_min
            while(minv>=k)//不断用 d_min 单独构成一个 k
            {
                printf("%d %d
    ",minb,k);
                minv-=k;
            }
            if(minv && s.size())//还没解完
            {
                it=s.end();it--;
                int maxv=(*it).first,maxb=(*it).second;
                s.erase(it);//取 d_max
                printf("%d %d %d %d
    ",minb,minv,maxb,k-minv);
                maxv-=k-minv;//配对
                if(maxv)
                    s.insert(make_pair(maxv,maxb));// d_max 还有剩余
            }
        }
    }//贪心处理
    void parti()
    {
        memset(pd,0,sizeof(pd));
        bitset<2*M+1> f[N];
        memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++)
            v[i]=d[i]-k;
        f[0].set(M);// 0  的位置初始化
        for(int i=1;i<=n;i++)
            if(v[i]>=0)
                f[i]|=f[i-1]<<v[i],f[i]|=f[i-1];
            else
                f[i]|=f[i-1]>>(-v[i]),f[i]|=f[i-1];//正负数分别处理,取或不取
        if(!f[n][M-k])//找不到满足条件的集合,无解
        {
            puts("-1");
            return ;
        }
        int now=M-k;
        s.clear();
        for(int i=n;i;i--)//倒着来,找到一个满足条件的集合
            if(f[i-1][now-v[i]])
            {
                s.insert(make_pair(d[i],i));
                pd[i]=1;now-=v[i];
            }
        solve();s.clear();
        for(int i=1;i<=n;i++)//找另一个集合
            if(!pd[i])
                s.insert(make_pair(d[i],i));
        solve();
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d%d",&n,&m,&k);
            for(int i=1;i<=n;i++)
                scanf("%d",&d[i]);
            if(m==n-2)
                parti();
            else
            {
                s.clear();
                for(int i=1;i<=n;i++)
                    s.insert(make_pair(d[i],i));
                solve();
            }
        }
        return 0;
    }
  • 相关阅读:
    WeX5 苹果APP打包教程
    开源中国社区
    HBuilder-飞速编码的极客工具,手指爽,眼睛爽下载
    java用double和float进行小数计算精度不准确
    SQL Server 查询表的主键的两种方式
    JS代码压缩格式化在线地址
    解决SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT'OpenRowset/OpenDatasource' 的访问的方法
    SQL跨数据库复制表数据
    ExtJs 扩展类CheckColumn的使用(事件触发)
    DM36x IPNC OSD显示中文 --- 基本数据准备篇
  • 原文地址:https://www.cnblogs.com/TEoS/p/13851698.html
Copyright © 2020-2023  润新知