Loading

[NOI2020]制作菜品

链接:https://www.luogu.com.cn/problem/P6775

题目描述:有m道菜,和n种原材料,每个原材料有一个质量di,有i=1ndi=m×k(m为正整数),每次在12道菜中选一部分满足质量总和为k,求一组合法方案。

题解:考场上想了3h会了70,结果m>n1判错WA55

假如我们已经选定好了一种方案T,那么我们可以考虑如何判定合法性,我们可以建出网络流模型,然后两边跑最大流,判是否满流。

但是我们会发现,若现在的判定方案为T,在跑网络流的时候,对于di>=k,我们可以将它流掉,那么它一定可以被单独做为1道菜流,所以我们可以将它流掉。

考虑流掉后m的范围,若m>=n1,由于有di<k,那么i=1ndi<=k×n,又有i=1ndi=k×m,则m<=n1,所以对于m>=n1可以归纳为m=n1

那么考虑如何构造一组m=n1的方案,可以如下构造方案:

先处理n个数,将最小的元素与最大的元素匹配从而消去最小的元素,这样就消去了一个数,然后对于剩下的数反复该过程。

考虑如何证明,令d从小到大排序后为D,则最小的数为D1,最大的数为Dn,那么因i=1nDi=(n1)×k,则若D1+Dn<k,那么有i=1nDi<=D1+(n1)×Dn<=(n1)×(D1+Dn)<(n1)×k,则矛盾,即D1+Dn>=k,可以构造出一组方案。

那么对于m=n2的情况,我们发现无法归纳解决,那么仔细观察可以发现有解时m=n2的解中一定会有两个不连通的m=n1

考虑如何证明。由于拆时将其拆为连通图,那么这些图都有m>=n1,那么若对于一个点数为N,边数为M的连通图,若M×k=i=1ndi,则即可归纳为m>=n1的问题,一定存在解。反之,则会无法恰好选完,则无解。
M×k=i=1Ndi为连通图有解的充要条件。

那么考虑我们m=n2的解是若干个连通图,每个图需满足上述条件,在这些图会存在一些带环图与树,那么将带环图与树合并,合并出的新连通图仍满足这个条件,那么合并会更优,在合并完后会合并成两颗无法合并的树,所以有解时一定会有两个不连通的m=n1

那么我们可以构造一颗树,另外一颗树就满足条件,即需构造(N1)×k=i=1Ndi,这个可化为k=i=1N(kdi),将kdi看作元素可以背包,转移时可用bitset优化转移。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
int read()
{
  char c=0;
  int sum=0;
  while (c<'0'||c>'9')
    c=getchar();
  while ('0'<=c&&c<='9')
    sum=sum*10+c-'0',c=getchar();
  return sum;
}
struct node
{
  int num,d;
  bool operator < (const node &a)const
  {
    return d<a.d;
  }
};
node point[501],tong[501];
int T,n,m,k,cnt,length,maxer,maxn;
bool used[501];
bitset<4980001>dp[501];
int main()
{
  bool op;
  T=read();
  while (T--)
    {
      n=read(),m=read(),k=read();
      for (int i=1;i<=n;++i)
	point[i].num=i,point[i].d=read();
      if (m>=n-1)
	{
	  cnt=0;
	  for (int i=1;i<=n;++i)
	    while (point[i].d>=k&&cnt<m-n+1)
	      cnt++,point[i].d-=k,printf("%d %d\n",point[i].num,k);
	  for (int t=1;t<=n;++t)
	    {
	      sort(point+t,point+n+1);
	      if (point[t].d==0)
			continue;
		  if (point[t].d==k)
		  {
		  	printf("%d %d\n",point[t].num,k);
		    continue;
		  }
	      printf("%d %d %d %d\n",point[t].num,point[t].d,point[n].num,k-point[t].d);
	      point[n].d-=(k-point[t].d);
	    }
	}
      else
	{
	  for (int i=1;i<=n;++i)
	    used[i]=0;
	  for (int i=0;i<=n;++i)
	    dp[i].reset();
	  dp[0][(n-2)*k]=1;
	  int s,ps,tmp;
	  op=0;
	  for (int i=1;i<=n;++i)
	    {
	      if (k-point[i].d>=0)
		dp[i]=dp[i-1]|(dp[i-1]<<(k-point[i].d));
	      else
		dp[i]=dp[i-1]|(dp[i-1]>>(point[i].d-k));
	      if (dp[i][k+(n-2)*k])
		{
		  op=1;
		  used[i]=1,s=k+(n-2)*k-(k-point[i].d),ps=i;
		  while (ps!=0)
		    {
		      tmp=ps;
		      for (int j=0;j<=tmp-1;++j)
			if (dp[j][s])
			  {
			    ps=j;
			    break;
			  }
		      used[ps]=1;
		      s=s-(k-point[ps].d);
		    }
		  break;
		}
	    }
	  if (op)
	    {
	     length=0;
             for (int i=1;i<=n;++i)
               if (used[i])
                 tong[++length]=point[i];
             for (int t=1;t<=length;++t)
               {
                  sort(tong+t,tong+length+1);
                  if (tong[t].d==0)
                    continue;
                  printf("%d %d %d %d\n",tong[t].num,tong[t].d,tong[length].num,k-tong[t].d);
                  tong[length].d-=(k-tong[t].d);
               }
	      length=0;
	      for (int i=1;i<=n;++i)
	        if (!used[i])
	           tong[++length]=point[i];
              for (int t=1;t<=length;++t)
	        {
	           sort(tong+t,tong+length+1);
	           if (tong[t].d==0)
		      continue;
		   printf("%d %d %d %d\n",tong[t].num,tong[t].d,tong[length].num,k-tong[t].d);
		   tong[length].d-=(k-tong[t].d);
		}
	   }
	  else
	    puts("-1");
	  }
    }
  return 0;
}
posted @   zhouhuanyi  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示