[JOISC2020] カメレオンの恋 题解

前言

题目传送门

没想到乱搞真给做出来了。

询问上限 \(2\times 10^4\),我最多的用了 \(19800+\)

思路

定义 \(c_i\) 表示 \(i\) 变色龙的原色。

首先一个直观想法是,每次询问一个点对 \((i,j)\),如果 \(\text{Query}(i,j)=1\),那么他就是一对。

思考一下,显然是不对的,因为有单相思的存在。

所以对于 \(\text{Query}(i,j)=1\),只有以下三种情况(显然 \(\text{Query}(i,j)=2\) 不会对答案造成任何影响):

  • \(c_i=c_j\)

  • \(i\) 喜欢 \(j\)

  • \(j\) 喜欢 \(i\)

看到这种情况,强迫症告诉你你应该去建个边。

考虑一个图,对于一个点对 \((i,j)\)(点对有序):

  • \(c_i=c_j\) 连一条无向边。

  • \(i\) 喜欢 \(j\)\(i\)\(j\) 连一条有向边。

在不考虑询问次数的情况下,我们只需要找到有向边就可以从而确定答案。

我们发现一个点最多只能与三条边相关联。

相当于说如果一个点 \(x\)

  • 它的度数为 \(1\),只有可能是 \(c_i=c_j\) 的那一条,直接就找到了原色相同的哪一个。

  • 它的度数为 \(2\),显然是一条 \(c_i=c_j\) 和一条单相思的边。但是显然这条单相思的边 \((x,y)\) 的双方必须满足 \(c_x\not= c_y\) 不然实际上与度数为 \(1\) 是等价的。在这种情况下,必然还存在一条 \(i\to j\) 或者 \(j\to i\) 的边,并且由于题目条件限制,双方原色不相等。可是这样的话就构成了第三条边,所以矛盾。故不存在度数为 \(2\) 的点。

  • 它的度数为 \(3\)。考虑与它相关联的点分别为 \(i,j,k\)。我们将这四个点三个三个配对进行 \(\text{Query}\),可以发现,只有喜欢 \(x\) 的点,\(x\),和与 \(x\) 原色相同的点才会 \(\text{Query}\) 之后为 \(1\),其他的都大于等于 \(1\)。故我们可以借此找到一条有向边 \(y\) 喜欢 \(x\)。至于 \(x\) 喜欢的那个点,显然会在那个点被找到,也就找到了所有有向边。

故此我们找到了所有有向边,并借此找到无向边,从而确定答案。

但是这样查询的话是次数是 \(\mathcal{O}(n^2)\) 的。

考虑进行一些优化。

第一时间想到一个分治的技巧,即对于一个点 \(i\),你想找到它的边。考虑在剩下的点(假定为 \([1,m]\) 这个区间中),你看 \([1,\dfrac{m}{2}]\)\(i\) 中是否有边,即 \(\text{Query}\) 是否等于点集的大小。如果有就分治这一块,否则就分治 \([\dfrac{m}{2} + 1, m]\) 这一块。你只需最多进行三次这样的分治操作即可找到确切的边。

但是你还是发现一个问题,就是说要是 \([1,\dfrac{m}{2}]\) 这一块中有边怎么办,你就不能直接判断大小相等来看他们与 \(i\) 是否有边了。所以这个分治的前提在于 \([1,m]\) 是个独立集。

所以考虑这样循环分治:

  • 在当前点集(最初就是 \([1,2\times n]\))中找到一个独立集。(找独立集就是枚举当前点集,如果能加入就加入,判断方法就是 \(\text{Query}\) 是否等于当前独立集大小 \(+1\)

  • 将当前点集减去这个独立集。

  • 枚举当前点集中的点,在独立集中进行分治,找到能连的边。

  • 如果当前点集为空,退出循环。

虽然的分治层数在 \(\mathcal{O}(\log n)\) 级别,但是你只要分治到了这么多层,就必然对应一条边,且边的总数在 \(\mathcal{O}(n)\) 级别,故总的询问次数也在 \(\mathcal{O}(n\log n)\) 级别。

但是显然存在一些常数问题。所以实现一定要精细。

具体见代码:

//洛谷代码与OJ代码不同
#include<bits/stdc++.h>
#include "chameleon.h"
using namespace std;
int ask(vector<int> &p, int x)
{
	if(p.size() == 1) return p[0];
	vector<int> q;
	int l = 0, r = p.size() - 1;
	int mid = (l + r) >> 1;
	for (int i = l; i <= mid; ++i) q.push_back(p[i]);
	q.push_back(x);
	if(Query(q) != q.size()) return q.pop_back(), ask(q, x);
	q.clear();
	for (int i = mid + 1; i <= r; ++i) q.push_back(p[i]);
	return ask(q, x);
}
void Solve(int N)
{
	int n = N;
	vector<int> ans[5005];
	set<int> p[10005], q;
	int id[10005];
	for (int j = 1; j <= 2 * n; ++j) id[j] = j, q.insert(j);
	while(q.size())
	{
		int now = *q.begin(); q.erase(now);
		vector<int> pvp;
		pvp.push_back(now);
		// random_shuffle(id + 1, id + 2 * n + 1);
		for (int qwq = 1; qwq <= 2 * n; ++qwq)
		{
			int i = id[qwq];
			if(!q.count(i)) continue;
			pvp.push_back(i);
			if(Query(pvp) != pvp.size()) pvp.pop_back();
			else q.erase(i);
		}
		for (int i = 1; i <= 2 * n; ++i)
		{
			if(!q.count(i)) continue;
			if(ans[i].size() >= 3) continue;
			set<int> del;
			while(ans[i].size() < 3)
			{
				vector<int> qp;
				for (int i = 0; i < pvp.size(); ++i) if(!del.count(pvp[i])) qp.push_back(pvp[i]);
				qp.push_back(i);
				if(Query(qp) == qp.size()) break;
				int id = ask(qp, i);
				ans[id].push_back(i), ans[i].push_back(id);
				p[id].insert(i), p[i].insert(id);
				del.insert(id);
			}
		}
	}
	for (int i = 1; i <= 2 * n; ++i)
	{
		if(ans[i].size() == 3)
		{
			vector<int> r;
			r.push_back(i), r.push_back(ans[i][0]), r.push_back(ans[i][1]);
			if(Query(r) == 1) p[i].erase(ans[i][2]), p[ans[i][2]].erase(i);
			r.clear();
			r.push_back(i), r.push_back(ans[i][0]), r.push_back(ans[i][2]);
			if(Query(r) == 1) p[i].erase(ans[i][1]), p[ans[i][1]].erase(i);
			r.clear();
			r.push_back(i), r.push_back(ans[i][2]), r.push_back(ans[i][1]);
			if(Query(r) == 1) p[i].erase(ans[i][0]), p[ans[i][0]].erase(i);
			r.clear();
		}
	}
	for (int i = 1; i <= 2 * n; ++i)
	{
		if(*p[i].begin() < i) Answer(*p[i].begin(), i);
	}
}
posted @ 2024-07-11 20:52  Saltyfish6  阅读(10)  评论(0编辑  收藏  举报
Document