[NOI2020] 制作菜品

一、题目

点此看题

二、解法

独立思考确实挺有趣的,虽然花了很久时间但还是挺开心的。

看到这题没什么思路,可以多看几遍题,发现关键条件 \(n-2\leq m\),还是不怎么会,直接开部分分。

\(n-1=m\) 怎么做啊?也就是原料数正好多一个,如果某个原料不足 \(k\),那么直接把他选完,再找到最大的原料选剩下的部分,这样可以一直递归下去,每次可以减少一个原材料,而且最后一定会剩下两个原材料和一次机会,这种情况一定有解,因为无论怎样最大的都可以让你凑足 \(k\)

\(n-1\leq m\) 都一样啊,略微改一下就行了,如果全部原料都大于等于 \(k\) 那么随便找一个减少 \(k\) 即可。


发现现在只剩下 \(n-2=m\) 了,但是不能直接用 \(n-1=m\) 的方法,这样会出问题:最后可能会剩三个原材料,而且每次不一定就能减少一个原材料(最大值不够顶)。

但是可以把它分成不相关的两个集合,每个集合用 \(n-1=m\) 的方法做即可,设划分的集合为 \(S\),那么有解当且仅当满足:\(\sum d_i=(|S|-1)\cdot k\),如果满足这个条件一定有解(充分性),现在考虑证明一下必要性:

仔细观察我们的基础问题 \(n-1=m\),可以看成 \(n-1\) 条边联通了 \(n\) 个点,因为构造的过程反应到图上就是给每个点找父亲,\(n-2=m\) 就意味着至少有两个连通块,那一定会有至少一个连通块满足点数等于边数减一,我们把剩下的点看成另一个连通块即可,这两个连通块是独立的,可以适用于"树"的必要条件。

现在的问题就是划分出一个满足 \(\sum d_i=(|S|-1)\cdot k\) 的集合了,用类似分数规划的技巧可以转化成 \(\sum d_i-k=-k\),那么设 \(dp[i][j]\) 表示考虑前 \(i\) 个原料 \(d_i-k\) 的总和为 \(j\) 是否合法,可以用 \(\tt bitset\) 优化这个 \(01\) 背包,时间复杂度 \(O(T\frac{n^2k}{w})\)

然后把 \(dp\) 路径还原一下就行了,所以除了一开始的结论剩下都是简单的技巧。

#include <cstdio>
#include <vector>
#include <bitset>
#include <set>
using namespace std;
const int M = 505;
const int N = 2500000;
#define fi first
#define se second
#define make make_pair
#define pii pair<int,int>
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,k,d[M],vis[M];vector<int> v;
bitset<10000*M> dp[M];
void solve()
{
	set<pii> s;
	for(int i=0;i<v.size();i++)
		s.insert(make(d[v[i]],v[i]));
	while(!s.empty())
	{
		pii t1=*s.begin(),t2=*s.rbegin();
		s.erase(t1);
		while(t1.fi>=k)
			t1.fi-=k,printf("%d %d\n",t1.se,k);
		if(!t1.fi) continue;
		s.erase(t2);
		int tmp=k-t1.fi;
		printf("%d %d %d %d\n",t1.se,t1.fi,t2.se,tmp);
		t2.fi-=tmp;
		s.insert(t2);
	}
	v.clear();
}
void devide()
{
	dp[0].reset();dp[0][N]=1;
	for(int i=1;i<=n;i++)
	{
		dp[i]=dp[i-1];
		if(d[i]>k)
			dp[i]|=dp[i-1]<<(d[i]-k);
		else
			dp[i]|=dp[i-1]>>(k-d[i]);
	}
	if(!dp[n][N-k]) puts("-1");
	else
	{
		int x=n,y=N-k;
		while(x)
		{
			if(dp[x-1][y-(d[x]-k)])
			{
				y-=(d[x]-k);
				vis[x]=1;
				v.push_back(x);
			}
			x--;
		}
		solve();
		for(int i=1;i<=n;i++)
			if(!vis[i]) v.push_back(i);
		solve();
	}
}
signed main()
{
	T=read();
	while(T--)
	{
		n=read();m=read();k=read();
		for(int i=1;i<=n;i++)
			d[i]=read(),vis[i]=0;
		if(m!=n-2)
		{
			for(int i=1;i<=n;i++)
				v.push_back(i);
			solve();
			continue;
		}
		devide();
	}
}
posted @ 2021-04-13 15:24  C202044zxy  阅读(77)  评论(0编辑  收藏  举报