zoukankan      html  css  js  c++  java
  • 【洛谷6775】[NOI2020] 制作菜品(思维好题)

    点此看题面

    • (n)种原材料,分别有(d_i)克,满足(sum d_i=m imes k)
    • 要求制作(m)道菜,满足每道菜使用至多两种原材料,且恰好使用(k)克。
    • 构造一种合法方案,或判断无解。
    • 数据组数(le10)(nle500,n-2le mle5 imes10^3)

    (m=n-1)的构造方案

    将所有(d_i)排序,然后就会发现两个很显然却很有用的结论:

    • (d_1<k)。如果(d_1ge k),则(sum d_ige nk>(n-1)k)
    • (n>2)时,(d_n>k-d_1)。如果(d_nle k-d_1),则(sum d_ile d_1+(n-1)(k-d_1)=(n-1)k-(n-2)d_1<(n-1)k)

    注意第二个结论虽然只适用于(n>2),但(n=2)的时候显然(d_1+d_n=k)直接构造就好了。

    而当(n>2)时,根据上面这两个结论,我们必然可以用光所有的(d_1),并从(d_n)中取出一部分与它配对。

    这样一来原材料少了一种((n)(1)),菜制成了一道((m)(1)),依然满足(m=n-1),可以继续按照这种方式构造下去。

    由于这种构造方案不取决于(d_i)的具体值,也就是说对于任意(m=n-1)的情况,都必然存在一种合法方案。

    (mge n)的直接转化

    如果(mge n),说明(sum d_i=mkge nk),因此(d_nge k)

    所以我们必然能用(d_n)制成一道菜((m)(1)),一直这样转化下去直至(m=n-1),然后就可以用上面的方法继续构造了。

    (m=n-2)的大力拆分

    如果我们在制成同一道菜的原材料之间连边,那么总共只能连出(m=n-2)条边,所以至少会被分成两个连通块。

    不同连通块中的原材料之间是互不影响的,而如果我们只考虑将所有原材料划分成两个集合(S_1,S_2),那么只要让两个集合分别制成(|S_1|-1)(|S_2|-1)道菜,根据先前的结论显然一定有合法方案,且拼起来又恰好是(n-2)道菜。

    因此,现在只要考虑如何把所有的原材料划分为两个集合,满足两个集合中的(d_i)的总和分别是((|S_1|-1) imes k)((|S_2|-1) imes k)

    由于全部(d_i)的总和就是((n-2) imes k),因此实际上只要满足其中一个条件,那么另一个条件自然也就满足了。

    所以现在的问题就是找到一个集合(S_1)满足(sum_{iin S_1}d_i=(|S_1|-1) imes k)

    把右式的(|S_1| imes k)移到左式的(sum)中去,就变成了(sum_{iin S_1}(d_i-k)=-k)

    这怎么看都是一个(01)背包问题啊!

    而这种仅需判有解、构造任意一组合法方案的问题,显然可以(bitset)优化。

    至此,所有的情况都讨论完了,不得不说这道题真的是一道思维好题。

    代码:(O(Tfrac{n^2k}{32}))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 500
    #define K 5000
    #define V (N*K)
    using namespace std;
    int n,m,k,a[N+5],id[N+5];I bool cmp(CI x,CI y) {return a[x]<a[y];}
    I void Solve(CI n,int* id)//用n种原材料制成n-1道菜,id是排序后的编号
    {
    	for(RI i=1,j;i<=n-1;++i)//制成n-1道菜
    	{
    		printf("%d %d %d %d
    ",id[i],a[id[i]],id[n],k-a[id[i]]),a[id[n]]-=k-a[id[i]];//把i和n拼成一道菜
    		for(j=n-1;j^i;--j) a[id[j]]>a[id[j+1]]&&(swap(id[j],id[j+1]),0);//最大值改变,维护大小顺序(显然可以堆优化,但无意义)
    	}
    }
    bitset<2*V+5> S[N+5];int s[N+5],p[N+5];I void Bag()//01背包划分集合
    {
    	RI i;for(S[0].set(V),i=1;i<=n;++i) a[id[i]]>k
    		?(S[i]=S[i-1]|S[i-1]<<a[id[i]]-k):(S[i]=S[i-1]|S[i-1]>>k-a[id[i]]);//bitset优化01背包
    	if(!S[n].test(V-k)) return (void)puts("-1");//无解
    	RI t=V-k;for(i=n;i;--i) p[i]=S[i-1].test(t-(a[id[i]]-k))?(t-=a[id[i]]-k,1):0;//找一组合法方案
    	for(t=0,i=1;i<=n;++i) p[i]&&(s[++t]=id[i]);Solve(t,s);//对于第一个点集
    	for(t=0,i=1;i<=n;++i) !p[i]&&(s[++t]=id[i]);Solve(t,s);//对于第二个点集
    }
    int main()
    {
    	RI Tt,i;scanf("%d",&Tt);W(Tt--)
    	{
    		for(scanf("%d%d%d",&n,&m,&k),i=1;i<=n;++i) scanf("%d",a+i),id[i]=i;
    		sort(id+1,id+n+1,cmp);W(m>=n) {for(printf("%d %d
    ",id[n],k),//把m≥n转化成m=n-1
    			a[id[n]]-=k,--m,i=n-1;i;--i) a[id[i]]>a[id[i+1]]&&(swap(id[i],id[i+1]),0);}//用n做一道菜,维护大小顺序
    		m==n-2?Bag():Solve(n,id);
    	}return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    Mac Finder 里新建文本
    SourceTree 无法查看组织仓库
    Android Gradle 指定 Module 打包
    Mac 下安装安卓 apk 文件
    Android 瘦身之道 ---- so文件
    Android Google AdMob 广告接入示例
    Java 垃圾回收(GC) 泛读
    [麻雀虽小]记 简易Markdown阅读器 开发全过程
    Git 版本回滚
    线程与进程的区别及其通信方式
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6775.html
Copyright © 2011-2022 走看看