P4005 Solution

发布时间 2024-01-06 16:52:43作者: AffectionateCat

Preface

一定要剪枝:如果搜到的答案 \(\geq\) 当前的最优答案就不要继续搜了!!!不剪枝跑 \(T = 100\) 只能沦为暴力同分!!!

Solution

  • 首先对于每组地铁站,有 \(8\) 种换乘情况。标注一遍:

  • 直接爆搜(\(\mathcal O(n8^{\frac{n}{2}})\))在 \(n \leq 44\) 的情况下不现实。考虑优化状态,具体地,我们可以先把 ①② 两个连线方式合并起来看(右下角截进了搜狗输入法的河童皮肤,无伤大雅):

  • 同理可以发现 ③④、⑦⑧ 产生的贡献在无论何时都是一样的,那么我们不妨丢掉 ④⑦ 考虑。现在状态数变成了 \(4\) 个,爆搜已经可以获得比较可观的分数了(\(\mathcal O(n4^{\frac{n}{2}})\))。考虑继续优化,但是我们已经无法再合并状态了。
  • 我们在搜索的时候将所有换乘站连线按照 \(L\) 排序,那么你会发现搜到一对站点时,对它产生交点贡献的只会和前面的换乘站的 \(R\) 有关系(这同样是因为我们在先前丢掉了 ④⑦ 而非 ③⑧)。
  • 更具体地,根据在 \(0\) 号线的「上方」的 \(R\) 和「下方」的 \(R\),我们可以把贡献分为两个部分。容易发现在剩下来的四种状态中,①⑧ 的贡献都是「上方」的 \(R\) 增加 \(1\),而 ③⑥ 反之。
  • 所以这样我们就把每次的抉择变成了两个:对「上方」的 \(R\) 贡献还是「下方」的 \(R\) 贡献。而对上方贡献时,新增的交点个数可以贪心地选择 ①⑧ 中较小的一个;下方同理。目前时间复杂度 \(\mathcal O(n2^{\frac{n}{2}})\)
  • 考虑如何优化贡献的计算:不难发现计算时产生交点的 \(R\) 是一段连续的区间,具体地:
  • 选择 ① 时,一个 \(R\) 贡献当且仅当:它在「上方」,它的 \(R\) 在当前区间 \([l, r]\) 内(开区间、闭区间可以灵活抉择,毕竟每个位置上只会有一个换乘站)。
  • 选择 ⑧ 时,一个 \(R\) 贡献当且仅当:它在「上方」,它的 \(R\) 在当前区间 \([r, \inf)\) 内,或它在「下方」,它的 \(R\) 在当前区间 \([l, \inf)\) 内。
  • 选择 ③ 时,一个 \(R\) 贡献当且仅当:它在「下方」,它的 \(R\) 在当前区间 \([l, r]\) 内。
  • 选择 ⑥ 时,一个 \(R\) 贡献当且仅当:它在「上方」,它的 \(R\) 在当前区间 \([l, \inf)\) 内,或它在「下方」,它的 \(R\) 在当前区间 \([r, \inf)\) 内。
  • 找每种贡献明显可以使用树状数组优化。时间复杂度 \(\mathcal O(2^{\frac{n}{2}}\log n)\)(但是一定要加上开头所述的优化!!!1)。

Code

  • \(8\) 个状态爆搜:没有写过。
  • \(4\) 个状态爆搜(交上去不是 TLE 而是 WA 36 pts,贡献可能算错了吧 /ll 我大为震撼):
#define MAXN 45
int lst[MAXN], status[MAXN];

struct Metro {
	int l, r;
	Metro () {}
	Metro (int L, int R) : l(L), r(R) {}
	const bool operator < (const Metro& k) const {
		return l < k.l;
	}
};

std::vector<Metro> lines;

// status = 0
//      -----------
//      |         |
// =====*=========*=====
//
//
// status = 1
//      -------------------
//      |                 |
// =====*=========*=====  |
//                |       |
//                ---------
// status = 2
//
//
// =====*=========*=====  
//      |         |       
//      -----------
// status = 3
//                ---------
//                |       |
// =====*=========*=====  |
//      |                 |
//      -------------------

int ans = 0x3f3f3f3f;
void dfs(int u = 0, int sum = 0) {
	if (sum >= ans) return;
	if (u == lines.size()) { ans = sum; return; }
	int up0 = 0, down1 = 0, down2 = 0, up3 = 0;
	for (int i = 0; i < u; ++i) {
		if (status[i] == 0) {
			up0 += lines[i].r < lines[u].r && lines[i].r > lines[u].l;
			down1 += lines[i].r > lines[u].l;
			up3 += lines[i].r > lines[u].r;
		} else if (status[i] == 1) {
		    down1 += lines[i].r > lines[u].r;
		    down2 += lines[i].r < lines[u].r && lines[i].r > lines[u].l;
			up3 += 1;
		} else if (status[i] == 2) {
		    down1 += lines[i].r > lines[u].r;
		    down2 += lines[i].r < lines[u].r && lines[i].r > lines[u].l;
			up3 += lines[i].r > lines[u].l;
		} else /*status[i] == 3*/ {
			up0 += lines[i].r < lines[u].r && lines[i].r > lines[u].l;
			down1 += 1;
			up3 += lines[i].r > lines[u].r;
		}
	}
	if (up0 < up3) 
		status[u] = 0, dfs(u + 1, sum + up0);
	else status[u] = 3, dfs(u + 1, sum + up3);
	if (down1 < down2) 
		status[u] = 1, dfs(u + 1, sum + down1);
	else status[u] = 2, dfs(u + 1, sum + down2);
}
int main() {
	int T = read<int>();
	while (T--) {
		int N = read<int>();
		for (int i = 1; i <= N; ++i) {
			int a = read<int>();
			if (lst[a]) lines.push_back(Metro(lst[a], i));
			lst[a] = i;
		}
		std::sort(lines.begin(), lines.end());
		dfs(0); print<int>(ans, '\n');
		memset(lst, 0, sizeof(lst));
		lines.clear(), ans = 0x3f3f3f3f;
	}
	return 0;
}
  • 树状数组满分做法,随便卡了点常数,其他部分都一样就不放了:
int t0[MAXN], t1[MAXN], Z = 45, ans = 0x3f3f3f3f;
void add0(int x, int v) { while (x <= Z) t0[x] += v, x += x & (-x); }
void add1(int x, int v) { while (x <= Z) t1[x] += v, x += x & (-x); }
int query0(int x) { int ans = 0; while (x) ans += t0[x], x -= x & (-x); return ans; }
int query1(int x) { int ans = 0; while (x) ans += t1[x], x -= x & (-x); return ans; }
void dfs(int u = 0, int sum = 0) {
	if (sum >= ans) return;
	if (u == lines.size()) { ans = std::min(ans, sum); return; }
	int l = lines[u].l, r = lines[u].r, pr0 = query0(r), pr1 = query1(r), pr2 = u - pr0 - pr1;
	pr0 -= query0(l), pr1 -= query1(l);
//	choose :
//	0 -> pr0
//	1 -> pr0 + pr2
//	2 -> pr1
//	3 -> pr1 + pr2
//	printf("%s%d %d %d %d %d\n", std::string(u * 4, ' ').c_str(), u, pr0, pr1, pr2, pr3);
//	printf("%s%d %d\n", std::string(u * 4, ' ').c_str(), std::min(pr0, pr1 + pr2), std::min(pr1, pr0 + pr3));
	add0(r, 1);
	dfs(u + 1, sum + std::min(pr0, pr1 + pr2));
	add0(r, -1), add1(r, 1);
	dfs(u + 1, sum + std::min(pr1, pr0 + pr2));
	add1(r, -1);
//	printf("%s%d\n", std::string(u * 4, ' ').c_str(), ans);
}