P6775 [NOI2020] 制作菜品

题目大意

厨师准备给小朋友们制作 m m m 道菜,每道菜均使用 k k k 克原材料。为此,厨师购入了 n n n 种原材料,原材料从 1 1 1 n n n 编号,第 i i i 种原材料的质量为 d i d_i di 克。 n n n 种原材料的质量之和恰好为 m × k m \times k m×k,其中 d i d_i di k k k 都是正整数
制作菜品时,一种原材料可以被用于多道菜,但为了让菜品的味道更纯粹,厨师打算每道菜至多使用 2 2 2原材料。现在请你判断是否存在一种满足要求的制作方案。更具体地,方案应满足下列要求:

  • 共做出 m m m 道菜。
  • 每道菜至多使用 2 2 2 种原材料。
  • 每道菜恰好使用 k k k 克原材料。
  • 每道菜使用的每种原材料的质量都为正整数克。
  • n n n 种原材料都被恰好用完。
    若存在满足要求的制作方案,你还应该给出一种具体的制作方案。

1 ≤ n ≤ 500 1 \leq n \leq 500 1n500 n − 2 ≤ m ≤ 5000 n - 2 \leq m \leq 5000 n2m5000 m ≥ 1 m \geq 1 m1 1 ≤ k ≤ 5000 1 \leq k \leq 5000 1k5000 ∑ i = 1 n d i = m × k \sum_{i=1}^{n}d_i = m \times k i=1ndi=m×k

题解

好题!
首先观察数据范围,发现 m ≥ n − 2 m\ge n-2 mn2,就知道这题应该是什么分类讨论题了
再观察细致的数据范围,发现有 m = n − 1 m=n-1 m=n1 m ≥ n − 1 m\ge n-1 mn1的部分分,所以从 m = n − 1 m=n-1 m=n1下手,先把 d d d数组排序,有结论 d 1 + d n ≥ k d_1+d_n\ge k d1+dnk,然后我们用 1 1 1 n n n做一道菜,把 1 1 1用完,然后就变成了一个 m − 1 , n − 1 m-1,n-1 m1,n1的规模更小的数据范围,当 m = 2 , n = 1 m=2,n=1 m=2,n=1时显然有解,所以 m = n − 1 m=n-1 m=n1时有解, m > n − 1 m>n-1 m>n1时,显然有 d n ≥ k d_n\ge k dnk,然后不停地用 d n d_n dn做菜,显然可以归约到 m = n − 1 m=n-1 m=n1的情况
然后就只剩下 m = n − 2 m=n-2 m=n2的情况了,手玩一下小数据进行观察, n = 3 n=3 n=3时无解, n = 4 n=4 n=4的充要条件为两个 d d d的和等于 k k k,这启发我们把 m = n − 2 m=n-2 m=n2的情况划分成两个 m = n − 1 m=n-1 m=n1的情况
结论: { d n } \{d_n\} {dn}有解当且仅当,可以划分成一个子集 S ⊂ { 1 , 2 , ⋯   , n } S\subset\{1,2,\cdots,n\} S{1,2,,n},使得 ∑ i ∈ S d i = ( ∣ S ∣ − 1 ) k \sum_{i\in S}d_i=(|S|-1)k iSdi=(S1)k
充分性:这个子集和它的补集可以形成两个 m = n − 1 m=n-1 m=n1的问题,所以肯定有解
必要性:如果两个原料做了一道菜,我们就给这两个原料连上一条边,由于 m = n − 2 m=n-2 m=n2,所以这个图肯定不连通,解必定有办法划分成两个连通的图
这个可以用 01 01 01背包来判断,用 b i t s e t bitset bitset优化,复杂度就变成了 O ( n 2 k w ) O(\frac{n^2k}{w}) O(wn2k)

code

#include
#include
#include
#include
#include
using namespace std;
void read(int &res)
{
	res=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while('0'<=ch&&ch<='9') res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
}
const int N=5e2+10,K=5e3+10;
int n,m,k,d[N+10];
struct node
{
	int id,d;
}p[N+10];
void write(int res)
{
	if(res>=10) write(res/10),putchar('0'+res%10);
	else putchar('0'+res);
}
namespace sub1
{
	bool cmp(node a,node b){return a.d<b.d;}
	void solve(int n)
	{
		for(int i=1;i<=n;i++)
			while(m>n-1&&p[i].d>=k)
			{
				write(p[i].id),putchar(' '),write(k),putchar('\n');
				p[i].d-=k;
				m--;
			}
		for(int i=n;i>=2;i--)
		{
			sort(p+1,p+1+i,cmp);
			if(p[1].d==0) write(p[i].id),putchar(' '),write(k-p[1].d),putchar('\n');	
			else write(p[1].id),putchar(' '),write(p[1].d),putchar(' '),write(p[i].id),putchar(' '),write(k-p[1].d),putchar('\n');
			p[i].d-=k-p[1].d,p[1]=p[i];
		}
	}
}
namespace sub2
{
	bitset<N*K*2+10> f[N+10];
	bool vis[N+10];
	const int C=N*K;
	void dfs(int h,int s)
	{
		if(h==0) return;
		if(s+C-(d[h]-k)>=0&&s+C-(d[h]-k)<=C*2&&f[h-1][s+C-(d[h]-k)]) vis[h]=true,dfs(h-1,s-(d[h]-k));
		else dfs(h-1,s);
	}
	void solve()
	{
		f[0].reset(),f[0].set(k+C);
		for(int i=1;i<=n;i++)
		{
			if(d[i]-k>=0) f[i]=f[i-1]|(f[i-1]<<d[i]-k);
			else f[i]=f[i-1]|(f[i-1]>>k-d[i]);
		}
		if(!f[n][C]) printf("-1\n");
		else
		{
			for(int i=1;i<=n;i++) vis[i]=false;
			dfs(n,0);
			int top=0;
			for(int i=1;i<=n;i++)
				if(vis[i])
					p[++top]=(node){i,d[i]};
			m=top-1;
			sub1::solve(top);
			top=0;
			for(int i=1;i<=n;i++)
				if(!vis[i])
					p[++top]=(node){i,d[i]};
			m=top-1;
			sub1::solve(top);
		}
	}
}
int main()
{
	freopen("dish.in","r",stdin);
	freopen("dish.out","w",stdout);
	int T;
	read(T);
	for(;T--;)
	{
		read(n),read(m),read(k);
		for(int i=1;i<=n;i++) read(d[i]);
		if(m>=n-1)
		{
			for(int i=1;i<=n;i++) p[i].d=d[i],p[i].id=i;
			sub1::solve(n);
		}
		else sub2::solve();
	}
	return 0;
}

你可能感兴趣的:(构造,算法)