[NOI2020]制作菜品

30 篇文章 0 订阅
14 篇文章 0 订阅

题目

传送门 to luogu

思路

吐槽:何必把 n ⩽ m + 2 n\leqslant m+2 nm+2 的约束藏起来呢?我看到了之后还是不会做啊 😫​

这个 + 2 +2 +2 看上去不像好人,先抹杀掉,找找规律。

n ⩽ m n\leqslant m nm

必然存在 d x ⩾ k d_x\geqslant k dxk 。求出 min ⁡ { d i } ⩾ k \min\{d_i\}\geqslant k min{di}k 的答案平凡。而 min ⁡ { d i } < k \min\{d_i\}<k min{di}<k 时,将其与 d x ⩾ k d_x\geqslant k dxk 凑出一道菜,递归至 n ′ ⩽ m ′ n'\leqslant m' nm 。可见其必有解。

n = m + 1 n=m+1 n=m+1

不会了。看神的博客吧。原来也可以递归啊,我是伞兵 😅

n = 2 n=2 n=2 时有解。重要的递归出口啊,我都没想过 😢

n > 2 n>2 n>2 时显然 min ⁡ { d i } < k \min\{d_i\}<k min{di}<k,并且 min ⁡ { d i } + max ⁡ { d i } > k \min\{d_i\}+\max\{d_i\}>k min{di}+max{di}>k 。后者于我而言并不显然。它事实上是 m k = ∑ d i ⩽ min ⁡ { d i } + ( n − 1 ) max ⁡ { d i } mk=\sum d_i\leqslant \min\{d_i\}+(n{-}1)\max\{d_i\} mk=dimin{di}+(n1)max{di} 移项得到的
min ⁡ { d i } + max ⁡ { d i } ⩾ k + ( m − 1 ) ( k − max ⁡ { d i } ) \min\{d_i\}+\max\{d_i\}\geqslant k+(m{-}1)(k-\max\{d_i\}) min{di}+max{di}k+(m1)(kmax{di})
max ⁡ { d i } ⩾ k \max\{d_i\}\geqslant k max{di}k 则原不等式成立,否则由上式知其仍成立。这真的很容易发现么 😭

因此我们可以将 min ⁡ { d i } \min\{d_i\} min{di} max ⁡ { d i } \max\{d_i\} max{di} 做一道菜,递归到 n ′ = m ′ + 1 n'=m'+1 n=m+1 。可见其必有解。

n = m + 2 n=m+2 n=m+2

得想办法将这种情况向 n = m + 1 n=m+1 n=m+1 的情况上靠。

由于做菜类似匹配,可以想到建边。因此 m = n − 2 m=n-2 m=n2 时必然存在 m = n − 1 m=n-1 m=n1 的连通块,并且恰有两个。只需要确定这两个连通块,内部就分别有解了。

这等价于选出集合 S S S 使得 ∑ i ∈ S d i = k ⋅ ( ∣ S ∣ − 1 ) \sum_{i\in S}d_i=k\cdot (|S|{-}1) iSdi=k(S1),或者变形一下就是 ∑ i ∈ S ( d i − k ) = − k \sum_{i\in S}(d_i{-}k)=-k iS(dik)=k 。这就是个背包问题。

背包大小 O ( n k ) \mathcal O(nk) O(nk),由于只存 b o o l \tt bool bool 值可用 b i t s e t \tt bitset bitset 优化,总复杂度 O ( n 2 k ω ) \mathcal O(\frac{n^2k}{\omega}) O(ωn2k)

当然我们同时注意到 ∑ i = 1 n ( d i − k ) = − 2 k \sum_{i=1}^{n}(d_i{-}k)=-2k i=1n(dik)=2k,这说明选出来的子集 S S S 的和与补集 ∁ S \complement S S 的和相等;那我们不妨以此为判据,或许好写一点。

代码

#include <cstdio> // Almighty OUYE yyds!!!
#include <algorithm>
#include <cstring>
#include <cctype> // isdigit
#include <vector>
#include <bitset>
#include <set>
#include <random>
using llong = long long;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
	return a*f;}

const int MAXN = 500;
struct Dish{
	int val, id;
	bool operator < (const Dish &t) const {
		return val == t.val ? id < t.id : val < t.val;
	}
};
Dish d[MAXN];

struct Scheme { int a, b, c, d; };
std::vector<Scheme> ans; int k;
inline void makeMeal(Dish &a, Dish &b){
	ans.push_back(Scheme{a.id,a.val,b.id,k-a.val});
	b.val -= k-a.val;
}
inline void makeMeal(Dish &a){
	ans.push_back(Scheme{a.id,k,0,0});
	a.val -= k;
}

void _solve_main(std::set<Dish> &shit){	
	while(!shit.empty()){
		Dish a = *shit.begin();
		shit.erase(shit.begin());
		if(a.val >= k){ // do it alone
			makeMeal(a);
			if(a.val) shit.insert(a);
			continue;
		}
		Dish b = *shit.rbegin();
		shit.erase(b);
		makeMeal(a,b);
		if(b.val) shit.insert(b);
	}
}
void solve1(int n){
	static std::set<Dish> shit;
	rep0(i,0,n) shit.insert(d[i]);
	_solve_main(shit);
}
void solve1(const std::vector<int> &buc){
	static std::set<Dish> shit;
	for(const int &x : buc) shit.insert(d[x]);
	_solve_main(shit);
}

const int PACK = MAXN*5000;
std::bitset<PACK> dp[MAXN+1];
std::vector<int> buc[2];
void trace_back(int x, int v){
	for(int opt=0; (--x)!=-1; ){ // step-back
		const int me = d[x].val-k; // package weight
		if(0 <= v+me && v+me < PACK && dp[x].test(v+me))
			v += me, buc[opt].push_back(x); // dec
		else if(me <= v && v-me <= PACK && dp[x].test(v-me))
			v -= me, buc[opt^1].push_back(x); // inc
		else if(me > 0) v = me-v, buc[opt^=1].push_back(x);
		else v = -me-v, buc[opt].push_back(x), opt ^= 1;
	}
}

int main(){
	std::mt19937 rnd; rnd.seed(114514);
	for(int T=readint(); T; --T){
		int n = readint(), m = readint();
		k = readint(); // global
		rep0(i,0,n) d[i].val = readint(), d[i].id = i+1;
		ans.clear(), ans.reserve(m);
		if(n <= m+1) solve1(n);
		else{ // package problem
			dp[0].reset(), dp[0].set(0);
			std::shuffle(d,d+n,rnd);
			rep0(i,0,n){
				int me = d[i].val-k; // package weight
				if(me < 0) me = -me; // the same behaviour
				dp[i+1] = (dp[i]<<me)|(dp[i]>>me);
				rep0(j,0,me) if(dp[i].test(j))
					dp[i+1].set(me-j); // negate
			}
			if(!dp[n].test(0)){ puts("-1"); continue; }
			buc[0].clear(), buc[1].clear(), trace_back(n,0);
			solve1(buc[0]), solve1(buc[1]);
		}
		for(const Scheme &xjx : ans){
			printf("%d %d",xjx.a,xjx.b);
			if(xjx.d) printf(" %d %d",xjx.c,xjx.d);
			putchar('\n');
		}
	}
	return 0;
}

后记

我遇到了一个 C++ 语言本身的问题:如果使用

using PII = std::pair<int,int>;
struct mystruct : public PII{
	int &a, &b;
	mystruct(): PII(), a(first), b(second) { }
};

编译可以通过,但是 a 的值会和 first 不一样!

我不知道这是为什么。上 Stackoverflow \text{Stackoverflow} Stackoverflow 都没查到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值