[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);
}
}