NOI2019 I 君的探险
I 君的探险
地下宫殿可以抽象成一张 \(N\) 个点、\(M\) 条边的无向简单图(简单图满足任意两点之间至多存在一条直接相连的边),洞穴从 \(0 \sim n − 1\) 编号。目前你并不知道边有哪些。
每个洞穴都拥有一个光源,光源有开启、关闭两种状态,只有当光源处于开启状态时它所在的洞穴才会被照亮。初始时所有的光源都处于关闭状态,而光源的状态只能用 I 君发现的神秘机关改变。更具体的,使用神秘机关可以进行如下四种操作:
-
向机关给定一个编号 \(x\),机关将会改变 \(x\) 号洞穴,以及与 \(x\) 号洞穴有通路直接相连的洞穴的光源状态。即原来开启的光源将会关闭;原来关闭的光源将会开启。
-
向机关给定一个编号 \(x\),机关将会显示当前 \(x\) 号洞穴光源的状态。
-
向机关给定两个编号 \(x, y\),表示你确定有一条连接 \(x\) 号洞穴与 \(y\) 号洞穴的通路,并让机关记录。
-
向机关给定一个编号 \(x\),机关将会判断与 \(x\) 号洞穴相连的通路是否都已被记录。
机关在完成上一次操作后才能进行下一次操作。机关不能随意使用,因此每种操作的使用次数都有限制,分别为 \(L_m, L_q, M, L_c\)。你的任务是,编写一个程序,帮助 I 君决定如何合理利用神秘机关,从而正确地找到这 \(M\) 条通路。
题解
将分部分分进行介绍。
测试点1~5
修改\(x\)之后查询\(x+1\sim N\)有没有变化即可。\(n-1\)次modify,\(\binom{N}{2}\)次query。
测试点6~9
使用分治。对当前集合内的点挨个扫描,亮了不管,没亮点亮,直到亮了\(\frac{N}{2}\)个为止。
这样集合就被分成了亮了的和没亮的两部分,分别递归处理即可。
\(N\log_2N\)次modify和query。
有一种更简单的方法,那就是以\(\frac{1}{2}\)的概率修改每个点。这样一对匹配点亮了和没亮的概率都是\(\frac{1}{2}\),复杂度相同。
测试点10~11
由于父节点编号小于子节点,所以对于单个节点\(x\)来说可以二分他的父节点。把\([1,mid]\)中的点都修改一遍然后查询一下\(x\)的状态就能确定\(x\)的父节点在不在\([l,mid]\)中。
对所有节点考虑,整体二分即可。
\(N\log_2N\)次modify和query。
测试点12~17
在无环图上,我们希望用一种“剥叶子”的过程,通过逐步删去度数为\(1\)的点,最后找到整个图的形态。
我们不妨记每个点的标号为\(1\sim N\),对每个\(k∈[1,\log_2N]\),我们把二进制下第\(k\)位为\(1\)的点拿出来MODIFY,然后QUERY全体点
- 在这个操作下,\(x\)号点颜色改变,当且仅当\(x\)关联了奇数个第\(k\)位为\(1\)的点
不难发现,对每个\(k\)做一遍这个过程后,我们可以得知每个点关联的全体点的标号异或和。显然这个过程花费\(N\log_2 N\)次MODIFY和QUERY
现在,记\(sum[i]\)为\(i\)关联的全体点(不包括自己)的异或和,考虑一个点\(x\)
-
若\(x\)度数为\(1\),那么\(sum[x]\)到\(x\)恰好有一条边
-
通过两次QUERY、一次MODIFY,可以判定是否存在一条\(sum[x]\)和\(x\)的边
-
若存在这样一条边,可以令\(sum[sum[x]]=sum[sum[x]]⊕x\)以及\(sum[x]=0\),然后“断开”这条边
造一个队列\(Q\),初始时每个点都在其中,每次从\(Q\)中取出一个点\(x\),检查\(x\)和\(sum[x]\)之间是否有一条边
- 若是,把\(sum[𝑥]\)加入\(Q\),并按如上所述过程断开边
重复上述过程直到\(Q\)为空
正确性:
- 若图中无环,当\(Q\)空的时候,显然所有的边都已被发现且处理
效率:
-
初始时\(Q\)中有\(N\)个点,每条边的发现会向\(Q\)中带来一个新的点。因此处理的点的个数不超过\(N+M≤2N−1\)
-
每次处理要求常数次MODIFY和QUERY,因此总消耗为\(N\)
总复杂度\(N\log_2N\)。
测试点18~25
考虑随机化,我们跟6~9一样,先随机修改\(\frac{N}{2}\)个点,把修改的集合记为\(S\)。
然后对所有点查询,如果为\(1\)那么一定与\(S\)中的有连边,把这些点记作集合\(T\)。
然后跟上面的整体二分一样,我们对\(S\)集合修改一半,如果之前\(T\)中的点有变动,那么肯定跟这一半有连边。
期望的复杂度是\(O(M\log M)\)。
namespace T1_5{
CO int N=500;
int val[N];
void main(int n,int m){
for(int i=0;i<n-1;++i){
modify(i);
for(int j=i+1;j<n;++j){
int x=query(j);
if(val[j]!=x) report(i,j),val[j]=x;
}
}
}
}
namespace T6_9{
CO int N=2e5;
int a[N],val[N];
void solve(int l,int r){
if(l>=r) return;
if(l+1==r) return report(a[l],a[r]);
for(int i=l;i<=r;++i)if(rand()&1) modify(a[i]);
for(int i=l;i<=r;++i) val[a[i]]=query(a[i]);
sort(a+l,a+r+1,[&](int a,int b)->bool{
return val[a]<val[b];
});
int mid=l-1;
for(int i=l;i<=r;++i)if(val[a[i]]==0) mid=i;
solve(l,mid);
solve(mid+1,r);
}
void main(int n,int m){
iota(a,a+n,0);
solve(0,n-1);
}
}
namespace T10_11{
CO int N=2e5;
int a[N],b[N];
void solve(int l,int r,int ql,int qr){
if(l>r) return;
if(ql==qr){
for(int i=l;i<=r;++i) report(a[i],ql);
return;
}
int mid=(ql+qr)>>1;
for(int i=ql;i<=mid;++i) modify(i);
int h=l-1,t=r+1;
for(int i=l;i<=r;++i){
if(a[i]<=mid or query(a[i])) b[++h]=a[i];
else b[--t]=a[i];
}
for(int i=ql;i<=mid;++i) modify(i);
copy(b+l,b+r+1,a+l);
solve(l,h,ql,mid);
solve(t,r,mid+1,qr);
}
void main(int n,int m){
iota(a,a+n,0);
solve(1,n-1,0,n-1);
}
}
namespace T12_17{ // base 1
CO int N=2e5;
int sum[N],vis[N];
set<pair<int,int> > edge;
void main(int n,int m){
for(int k=0;1<<k<=n;++k){
for(int i=1;i<=n;++i)if(i>>k&1) modify(i-1);
for(int i=1;i<=n;++i) sum[i]^=(query(i-1)^(i>>k&1))<<k;
for(int i=1;i<=n;++i)if(i>>k&1) modify(i-1);
}
deque<int> que;
for(int i=1;i<=n;++i) que.push_back(i),vis[i]=1;
while(que.size()){
int x=que.front();
que.pop_front(),vis[x]=0;
if(sum[x]<1 or sum[x]>n or sum[x]==x) continue;
int v0=query(sum[x]-1);
modify(x-1);
int v1=query(sum[x]-1);
if(v0==v1 or edge.count({min(x,sum[x]),max(x,sum[x])})) continue;
report(x-1,sum[x]-1);
edge.insert({min(x,sum[x]),max(x,sum[x])}); // edit 1
sum[sum[x]]^=x;
if(!vis[sum[x]]) que.push_back(sum[x]),vis[sum[x]]=1;
sum[x]=0;
}
}
}
namespace std{
template<>
struct hash<pair<int,int> >{
IN size_t operator()(CO pair<int,int>&a)CO{
return (int64)a.first<<32^a.second;
}
};
}
namespace T18_25{
CO int N=2e5;
unordered_set<pair<int,int> > edge;
vector<pair<int,int> > buc;
vector<int> to[N];
int chk[N],val[N];
void link(int x,int y){
if(x>y) swap(x,y);
if(edge.count({x,y})) return;
report(x,y);
edge.insert({x,y});
buc.push_back({x,y});
chk[x]=check(x),chk[y]=check(y);
}
void flip(int x){
val[x]^=1;
for(int y:to[x]) val[y]^=1;
modify(x);
}
void solve(vector<int> a,vector<int> b){
if(b.empty()) return;
if(a.size()==1){
for(int x:b) link(x,a[0]);
return;
}
vector<int> la,ra,lb,rb;
for(int x:a){
if(rand()&1) flip(x),la.push_back(x);
else ra.push_back(x);
}
for(int x:b){
if(query(x)!=val[x]) lb.push_back(x);
else rb.push_back(x);
}
for(int x:la) flip(x);
solve(la,lb);
solve(ra,rb);
}
void main(int n,int m){
while((int)edge.size()<m){
for(CO pair<int,int>&e:buc)
to[e.first].push_back(e.second),to[e.second].push_back(e.first);
buc.clear();
vector<int> a,b;
for(int i=0;i<n;++i)if(!chk[i])
if(rand()&1) a.push_back(i),flip(i);
for(int i=0;i<n;++i)if(!chk[i])
if(query(i)!=val[i]) b.push_back(i);
for(int x:a) flip(x);
solve(a,b);
}
}
}
void explore(int n,int m){
srand(20030506);
if(n<=500) return T1_5::main(n,m);
if(n%10==8) return T6_9::main(n,m);
if(n%10==7) return T10_11::main(n,m);
if(n%10==6 or n%10==5) return T12_17::main(n,m);
T18_25::main(n,m);
}