洛谷 P6775 - [NOI2020] 制作菜品(找性质+bitset 优化 dp)

题面传送门

好久没写过题解了,感觉几天没写手都生疏了

首先这种题目直接做肯定是有些困难的,不过注意到题目中有个奇奇怪怪的条件叫 \(m\ge n-2\),我们不妨从此入手解决这道题。

我们先来探究 \(m=n-1\) 的情况,观察大样例可知这种情况一定有解,我们不妨考虑这样一个贪心:假设 \(x\) 为使 \(d_i\) 取到最小值的 \(i\)\(y\) 为使得 \(d_i\) 取到最大值的 \(i\),那么我们就用 \(d_x\) 个原料 \(x\)\(k-d_x\) 个原料 \(y\) 制作成一个菜品,这样会消耗掉一种菜品(\(x\)),重复 \(n-1\) 轮即可。

为什么这样贪心是对的?我们考虑一次操作,为了证明这次操作是合法的,我们需证明以下两个结论:

  • \(d_x<k\)
  • \(k-d_x\le d_y\),即 \(d_x+d_y\ge k\)

这两个结论都可采用反证法证明。关于结论一,假设命题不成立,那么 \(d_x\ge k\),而由 \(d_x\) 的定义知 \(d_i\ge d_x\ge k\),故 \(\sum\limits_{i=1}^nd_i\ge nd_x\ge nk>mk\),矛盾。关于结论二,假设命题不成立,那么 \(d_y<k-d_x\),由 \(d_y\) 的定义知 \(d_i\le d_y<k-d_x\),故 \(\sum\limits_{i=1}^nd_i<(n-1)(k-d_x)+d_x=k+(n-2)(k-d_x)<k+(n-2)k=(n-1)k\),矛盾!

因此每次操作都是合法的。而显然每次操作之后都会有一个原料被消耗尽,因此我们就由 \(n\) 的情况过渡到了 \(n-1\) 的情况。又当 \(n=2\) 时有 \(d_1+d_2=k\),可以直接把两种原料搞在一起,符合条件。由数学归纳法可知这个贪心是没问题的。

接下来再考虑 \(m\ge n\) 的情况,我们考虑将 \(m\ge n\) 的情况向 \(m=n-1\) 的情况过渡,还是设 \(x\) 为使 \(d_i\) 取到最小值的 \(i\)\(y\) 为使得 \(d_i\) 取到最大值的 \(i\),这里有一个显然的结论,那就是 \(d_y\ge k\),否则 \(\sum\limits_{i=1}^nd_i<nk\le mk\),矛盾。因此我们只需每次选取 \(d_i\) 最大的 \(i\) 并消耗掉 \(k\)\(i\) 原料,直到 \(m=n-1\) 为止即可。

最后考虑 \(m=n-2\) 的情况,再次观察样例可知这种情况就不一定有解了,那 \(m=n-2\) 的情况究竟什么时候有解,什么时候无解呢?

又到了考验选手观察能力的时候了,这里还有第四个结论,那就是 \(m=n-2\) 的情况有解当且仅当存在 \(S\subset\{1,2,3,\cdots,n\}\) 满足 \(\sum\limits_{x\in S}d_x=k(|S|-1)\)

证明:充分性显然,记全集为 \(U=\{1,2,3,\cdots,n\}\),既然 \(S\) 满足 \(\sum\limits_{x\in S}d_x=k(|S|-1)\),那么 \(T=U-S\) 也一定满足 \(\sum\limits_{x\in T}d_x=k(|T|-1)\),因此我们只需对 \(S,T\) 分别执行 \(m=n-1\) 的操作即可。必要性:我们假设对于某个序列 \(d_1,d_2,\cdots,d_n\) 存在符合要求的解,我们考虑对于两个原料 \(i,j\),如果它们曾共同作为原料出现在这 \(n-2\) 个菜品中的某一个中,那么就连一条边 \((i,j)\),特别地如果一种原料单独做成一道菜品就连一条自环。显然这样会得到一张图 \(G=(V,E)\),并且 \(|V|=n,|E|\le n-2\),因此 \(G\) 不连通,而显然 \(G\) 中一定存在一个连通块是一棵树(否则假设所有联通块都存在环,那么边数必然 \(\ge n\))我们假设构成这个连通块的点集为 \(S\),这就是我们要找的集合 \(S\)。因此如果存在合法的方案,就必定存在符合要求的集合 \(S\)

那么怎样找出这样的集合 \(S\) 呢?考虑对 \(\sum\limits_{x\in S}d_x=k(|S|-1)\) 进行变形,两边同时减去 \(k|S|\) 可得 \(-k|S|+\sum\limits_{x\in S}d_x=-k\),再将 \(-k\) 分配到求和号中可得 \(\sum\limits_{x\in S}d_x-k=-k\),这个长得一脸 01 背包的样子。我们考虑 \(dp_{i,j}\) 为当前考虑到前 \(i\) 个数,是否存在一个集合 \(S\) 使得 \(\sum_{x\in S}d_x-k=j\),按照就的 01 背包的套路即可,这样复杂度是 \(n^2k\) 的,可以拿到 \(85\) 分。不过发现 \(dp\) 数组每一个值的取值都只有 01 两种可能,故考虑 bitset 优化 \(dp\),具体来说我们对开一个 bitset<MAXN*MAXK*2+5> dp[MAXN+5],其中第 \(i\)bitset 的第 \(j\) 位为 \(1\) 表示 \(dp_{i,j}=1\),否则表示 \(dp_{i,j}=0\)。对于形如 \(dp_{i,j}|=dp_{i-1,j-x}\) 的转移方程,我们就令 \(dp_i|=dp_{i-1}<<x\),显然二者是等价的,复杂度也就降到了 \(\dfrac{n^2k}{\omega}\)

求完 bitset 之后检验 \(dp_{n,-k}\) 是否等于 \(1\),如果 \(dp_{n,-k}=0\) 则无解,否则按照输出路径的套路找出符合要求的集合 \(S\),然后用 \(m=n-1\) 的算法输出方案即可。

u1s1 bitset yyds!

const int MAXN=500;
const int MAXK=5e3;
int n,m,k,a[MAXN+5];
void work(vector<pii> vx,int n,int m){
	set<pii> st;
	for(int i=0;i<vx.size();i++) st.insert(vx[i]);
	for(int i=1;i<=m;i++){
		if(m>=n){
			pii pp=*st.rbegin();st.erase(st.find(pp));
			printf("%d %d\n",pp.se,k);--m;st.insert(mp(pp.fi-k,pp.se));
		} else {
			pii p1=*st.begin();st.erase(st.find(p1));
			pii pn=*st.rbegin();st.erase(st.find(pn));
			printf("%d %d %d %d\n",p1.se,p1.fi,pn.se,k-p1.fi);
			st.insert(mp(pn.fi-(k-p1.fi),pn.se));
		}
	}
}
bitset<MAXN*MAXK*2+5> dp[MAXN+5];
void clear(){
	for(int i=0;i<=n;i++) dp[i].reset();
}
vector<pii> v1,v2;
void findpath(int x,int v){
	if(!x) return;
	if(dp[x-1][v]){
		v1.pb(mp(a[x],x));
		findpath(x-1,v);
	} else {
		v2.pb(mp(a[x],x));
		findpath(x-1,v-(a[x]-k));
	}
}
void solve(){//remember to make it first
	scanf("%d%d%d",&n,&m,&k);clear();
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	if(m>=n-1){
		vector<pii> v;
		for(int i=1;i<=n;i++) v.pb(mp(a[i],i));
		work(v,n,m);return;
	} int delta=n*k;dp[0][delta]=1;
	for(int i=1;i<=n;i++){
		int t=a[i]-k;
		if(t>=0) dp[i]=dp[i-1]|(dp[i-1]<<t);
		else dp[i]=dp[i-1]|(dp[i-1]>>(-t));
	}
	if(!dp[n][delta-k]){puts("-1");return;}
	v1.clear();v2.clear();findpath(n,delta-k);
//  for(int i=0;i<v1.size();i++) printf("%d ",v1[i].se);printf("\n");
//  for(int i=0;i<v2.size();i++) printf("%d ",v2[i].se);printf("\n");
	work(v1,v1.size(),v1.size()-1);
	work(v2,v2.size(),v2.size()-1);
}
int main(){
	int qu;scanf("%d",&qu);
	for(int i=1;i<=qu;i++) solve();
	return 0;
}
posted @ 2021-03-30 15:40  tzc_wk  阅读(118)  评论(0)    收藏  举报