SCP-J T3

\(\text{Preface\ \& Knowledge}\)

好久没写题解了,今天写一发。

难度:普及+/提高

知识点:动态规划

一道非常好的题目,拜谢出题人。顺便说一下,这场比赛都出得非常非常好。

暂且称两个人叫做小 W 和小 K。

\(\text{Key}\)

以下 \(3\) 个性质对于本题的解决相当重要,但其实相当简单发现 。

问题 \(1\):如果我们确定了一种分配方案,应该如何计算答案?

对于一个人来说,我们可以把他的移动分成两部分:

  • 移动自己所在的行:从第 \(0\) 行移到自己需要拿书的最大行最优,令最大行为 \(mar\),则花费是 \(2mar\)
  • 移动自己所在的列:对于每一行,我们都要分别移动自己的列到自己在这一行内需要拿书的最大列,否则要么会出现往返导致花费增加,要么就会跑不需要跑的导致花费增加,要么就拿不到这行的所有书。

观察到此性质后,我们枚举把每一本书分给谁,复杂度 \(O(2^nn)\),可以通过测试点 \(1\),预期得分 \(10\) 分。

性质 \(1\):每一行只会由两个人中的一个人负责全部书,否则答案不优。

从对列的移动部分来说,总有 \(1\) 个人会移动到一行中最右边的,如果由 \(2\) 个人负责,则 \(1\) 个人对答案的贡献是一行中最右边的元素位置乘 \(2\) 再加上其他数字,否则只有一行中最右边的元素位置乘 \(2\),因此 \(1\) 行由 \(1\) 个人负责总是最优的。

性质 \(2\):如果一行上面没有书,没有人会去到那行。

从行的移动部分来说,那部分是无用的,因为我们拿到任何书都不需要经过那行,没有必要去。

同时我们可以直接不考虑来回中的回,只需要考虑去到书架的部分,并不考虑回来,这样可以将时间空间复杂度加上一个 \(\frac{1}{2}\) 的常数。

\(\text{Part 0}\)

我们考虑使用 \(w_i\) 记录移动到一行上对列的移动部分的代价,则用 \(a_{x,y}\) 表示 \((x,y)\) 是否存在,存在为 \(1\) 不存在为 \(0\)

\[w_i=\max^{500}_{j=1}[a_{i,j}=1]j \]

实现时不需要这么麻烦,对于所有 \(r_i,c_i\),加入进来就让 \(w_{r_i}=\max(w_{r_i},c_i)\) 即可。

\(\text{Part 1}\)

我们考虑表示当前的局面并且使用动态规划数组表示这种局面有没有可能发生,我们现在只需要把每一行分配给两个人中的一个,如果说一个人上一个整理的是第 \(x\) 行,现在要整理第 \(y\) 行,则行花费的价值是 \(y-x\),但是我们不知道上一个从哪里转移过来,因此直接基于花费的状态难以设计,我们考虑如何使得我们的动态规划不需要靠行上的移动。

\(\text{Part\ 2}\)

我们发现总有 \(1\) 个人会去到最上面有书本的那行,另外 \(1\) 个人的行上的移动则是他去到过的最上面的那行。因此,我们考虑枚举小 W 移动到的最高行 \(R\)\(R\) 下面的行两个人都有可能拿到,上面的则全部由小 K 管理,前 \(R\) 行的局面可以表示为小 W 处理完了前 \(r\) 行,耗费了时间 \(x\),小 K 耗费了时间 \(y\),有转移方程:

\[dp_{r,x,y}=dp_{r-1,x-w_i,y}\operatorname{or} dp_{r-1,x,y-w_i} \]

综上,我们设计出了时间复杂度 \(O(r^3c^2)\) 的算法,时间复杂度有一点高。

\(\text{Part\ 2.5}\)

我们发现 \(r\) 可以直接滚动掉,这一点虽然没有优化我们的时间复杂度,但是优化了我们的空间复杂度,现在转移方程为

\[dp_{x,y}=dp_{x-w_i,y}\operatorname{or} dp_{x,y-w_i} \]

\(\text{Part\ 3}\)

我们发现一个重要性质:无论每一行给谁管理,\(w_i\) 的和是恒定的

因此,我们根本不需要在状态内加入 \(x\)\(y\) 两个维度,只需要 \(x\) 一个维度即可。最后我们可以发现,小 W 耗费了时间 \(x+R\),则小 K 花费的时间:

  • 首先从行的移动上来说,耗费了 \(\max(r_1,r_2,\cdots,r_n)\)
  • 从列的移动上来说:
    • \(R\) 上面的行:后缀和预处理即可,耗费了 \(\sum^n_{i=R+1}w_i\)
    • \(R\) 下面的行:拿总和减去 \(x\),耗费了 \(\sum^R_{i=1}w_i-x\)
  • 至于枚举 \(R\),并不需要每次都重新从 \(1\)\(R\) 做一次背包,枚举 \(R\) 的过程中每次都看作加入了一个 \(w_i\),更新动态规划数组即可。
  • 最后更新答案即可。

时间复杂度 \(O(Tr^2c)\),足够通过本题。

\(\text{Extra Part}\)

不难发现动态规划的取值只有 \(0\)\(1\),背包问题又是经典的可以用 \(\text{bitset}\) 优化的问题之一,所以我们考虑使用 \(\text{bitset}\) 优化,时间复杂度 \(O(\frac{Tr^2c}{w})\),遥遥领先于时间限制。

\(\text{Code}\)

#include <bits/stdc++.h>
using namespace std;
int T,n,r[100005],c[100005],ma[505],dp[3000005],w[505],suf[505],sum[505];
namespace DoubleQLzn 
{
	void solve()
	{
		cin >> n;
		int mar = 0,mac = 0;
		for (int i = 1;i <= n;i++) 
		{
			cin >> r[i] >> c[i];
			mar = max(mar,r[i]);
			mac = max(mac,c[i]);
      }
		for (int i = 1;i <= 500;i++) ma[i] = 0;
		for (int i = 1;i <= 500;i++) w[i] = 0;
		for (int i = 0;i <= 500;i++) suf[i] = 0;  
		for (int i = 0;i <= 500;i++) sum[i] = 0;  
		for (int i = 0;i <= 250000;i++) dp[i] = 0;
		for (int i = 1;i <= n;i++) ma[r[i]] = max(ma[r[i]],c[i]);
		for (int i = 1;i <= 500;i++) w[i] = ma[i];
		for (int i = 500;i >= 1;i--) suf[i] = suf[i + 1] + w[i];
		for (int i = 1;i <= 500;i++) sum[i] = sum[i - 1] + w[i];
		dp[0] = 1;
		int mi = INT_MAX;
		for (int i = 1;i < mar;i++) 
		{
			for (int j = 250000;j >= w[i];j--) dp[j] |= dp[j - w[i]];
			
			for (int j = 0;j <= 250000;j++)
			{
				if (dp[j]) mi = min(mi,max(i + j,mar + suf[i + 1] + sum[i] - j));
			}
		}
		cout << mi * 2 << '\n';
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T;
	cin >> T;
	while (T--) DoubleQLzn::solve();
	return 0;
}
posted @ 2025-12-28 17:03  lzn_tops  阅读(4)  评论(0)    收藏  举报