「NOI2019」I 君的探险
「NOI2019」I 君的探险
Part 20
朴素的 \(n^2\) 暴力。
每次枚举一个点 \(i\)。如果 \(i\) 的所有边都找到了直接跳过它,否则 modify 它一次。然后枚举所有 \(j\)。看看 \(j\) 的状态有没有被改变,改变了就 report。
但是这样第五个点过不去。优化一下,枚举 \(j\) 只要枚举大于 \(i\) 的就好了。这样次数肯定在给定范围内。
Part 44
A,B 两个性质的写法类似。
观察操作次数肉眼看出来需要 $n\log n $ 级别的。
先考虑性质 \(A\) 。只跟一个点有关,我们对于一个点二分一下,将 \([0,mid]\) 全部 modify 一下,然后看看这个点有没有状态变化即可(注意如果这个点和它连接的点都在 \([0,mid]\) 之间的话他的状态也是不会改变的)。复杂度 \(n^2\log n\) 。整体二分优化到 \(n\log n\)。
考虑性质 \(B\)。同样发现在 \([0,i-1]\) 的范围内与 \(i\) 相连的点只有一个。所以也可以二分。和性质 A 的区别就是性质 A 中每个点二分的边界是 \([0,n-1]\),而性质 B 的边界是 \([0,i-1]\)(边界就是初值)。
实现精细,减少不必要的操作即可获得 \(44\) pts。
Part 68
注意到 A,B,C,D 四个性质都可以归纳到一个性质:无环图。
那么也就是图中肯定会存在度数为 \(1\) 的点,考虑每次找出度数为 \(1\) 的点以及其边。
这是一个类似于拓扑排序的过程,
注意到 modify 实际上是一个异或的操作,所以我们可以采取这么一种策略:
按位进行 modify 操作,枚举第 \(k\) 位,将所有的第 \(k\) 位上为 \(1\) 的点进行一次 modify。
在这种操作下, \(x\) 的状态改变当且仅当跟其相连的点(包括其自己)的编号的异或和在 \(k\) 位为 \(1\)。
通过这种策略我们可以在 \(O(n\log n)\) 的时间内求得每一个点以及与其相连的点的异或和。
消耗的操作数也是 \(O(n\log n)\)。
设 \(S_x\) 表示与 \(x\) 相连(不包括自己)所有点的异或和。
我们对每个点检查 \(S_x\) 与 \(x\) 是否有边(通过两次 modify 和一次 query)
注意到如果 \(x\) 的度为 \(1\)。那么 \(x\) 和 \(S_x\) 之间肯定存在边。否则仅是有可能存在。但是这并不影响,因为我们的目的就是找出度数为 \(1\) 的点,这个方法能保证找出所有度为 \(1\) 的点就足够了。
所以我们整一个队列,初始所有点都在里面,每次取出一个点,然后如果 \(S_x\) 与 \(x\) 有边将 \(S_x\) 和 \(x\) 都塞入队列(因为取出来的点不仅可能是度为 \(1\) 的),并且更新 \(S_x\) 和 \(S_{S_x}\)。
重复上述过程即可。
上述过程实际上就是将度为 \(1\) 的点的边一个个 “断开”,从而产生新的 “度” 为 \(1\) 的点。当然也可能会将一些度不为 \(1\) 的点之间的边断开,但是这并不影响我们的正确性。
注意到总的入队数是 \(O(n+m)\) 的。操作消耗也是 \(O(n+m)\)。
总的复杂度就是 \(O(n\log n+n+m)\)。因为常数问题,操作数的消耗实际上比较大,所以对于性质 A,B 的点实际上是不能通过的。
Part 100
上述算法只能用在无环图中,它相当于是从图中找突破口,但是在有环图中可能根本没有突破口的存在。所以我们必须舍弃这种做法。
还是考虑 Part 44 的做法,注意到这个做法是正确的是因为我们给每个点找到了一个范围,使得范围里只有 \(1\) 个点会对这个点产生影响。
将这个性质放宽,注意到 B 性质的做法是依据 \([0,i-1]\) 范围内只有一个点。也就是向前连了 \(1\) 条边。
我们将其扩展,如果一个点向前连了奇数条边,我们用整体二分,可以找到一条边。
正确性:
假设在 \([l,r]\) 这个范围内有奇数个点,设 \(mid=\lfloor\dfrac{l+r}{2}\rfloor\)。我们检验 \([l,mid]\) 间是不是有奇数个,如果没有则 \([mid+1,r]\) 里一定有奇数个(奇数只能拆成一奇一偶相加)。
在一个排列中,我们称 \(A_i\) 这个点是好的,当且仅当有奇数个 \(A_j,j\in[0,j-1]\) 与 \(A_i\) 有边。
一个自然的想法是找到一个排列有尽可能多的好的点。然后找边,去掉找到的边。重复找排列,删边的过程。(删边的意思是去除这条边的影响)。
看起来好像很难。但是我们还有最后的策略:随机化。
一个结论:当图中不存在孤立点是,随意一个排列后,期望下,这个排列有至少 \(\dfrac{n}{3}\) 个点是好的。
但是我不会证。搬个别人的说法:
根据期望的线性,考虑每个点的贡献,对一个度数为 \(k\) 的点,有 \(\dfrac{\lfloor\dfrac{k}{2}\rfloor}{2}\) 的概率是好的,当 \(k=2\) 时有最小值 \(\dfrac{1}{3}\)。
我们随机排列,然后整体二分,去掉 “删边” 后产生的孤立点。重复上述过程直至所有边都删掉为止。
考虑时间复杂度。
每次期望找到 \(O(n)\) 条边,而做一次是 \(O(n\log n)\) 的,所以我们可以看做平均花费了 \(O(\log n)\) 去删除一条边。复杂度就是 \(O(m\log n)\)。
代码如下:
#include "explore.h"
#include <bits/stdc++.h>
using namespace std;
const int MAXN =2e5+5;
int n,m;
namespace pt20
{
map<pair<int,int>,bool> mp;
int sta[MAXN];
void Solve()
{
for(int i=0;i<n;++i)
{
if(check(i)) continue;
modify(i);
for(int j=i;j<n;++j)
{
if(i==j)
{
sta[j]^=1;
continue;
}
int cur=query(j);
if(sta[j]^cur)
{
int u=i,v=j;
if(!mp[make_pair(u,v)]) report(u,v);
mp[make_pair(u,v)]=1;
}
sta[j]=cur;
}
}
return ;
}
}
namespace pt36
{
int A[MAXN],B[MAXN],sta[MAXN],cur[MAXN],L[MAXN],R[MAXN];
void Binary(int le,int ri,int l,int r)
{
if(l>r||le>ri) return ;
if(le==ri)
{
for(int i=l;i<=r;++i)
if(A[i]<le) report(A[i],le);
return ;
}
int mid=le+ri>>1;
for(int i=le;i<=mid;++i) modify(i);
for(int i=l;i<=r;++i) cur[A[i]]=query(A[i]);
int hl=0,hr=0;
for(int i=l;i<=r;++i)
{
if((le<=A[i]&&A[i]<=mid)^(cur[A[i]]^sta[A[i]])) L[++hl]=A[i];
else R[++hr]=A[i];
}
for(int i=le;i<=mid;++i) sta[i]^=1;
for(int i=l;i<=r;++i) sta[A[i]]=cur[A[i]];
for(int i=l;i<l+hl;++i) A[i]=L[i-l+1];
for(int i=l+hl;i<=r;++i) A[i]=R[i-l-hl+1];
Binary(le,mid,l,l+hl-1);Binary(mid+1,ri,l+hl,r);
}
void Solve()
{
for(int i=1;i<=n;++i) A[i]=i-1;
Binary(0,n-1,1,n);
}
}
namespace pt44
{
int A[MAXN],B[MAXN],sta[MAXN],cur[MAXN],L[MAXN],R[MAXN];
void Binary(int le,int ri,int l,int r)
{
if(l>r||le>ri) return ;
if(le==ri)
{
for(int i=l;i<=r;++i)
if(le<A[i]) report(A[i],le);
return ;
}
int mid=le+ri>>1;
for(int i=le;i<=mid;++i) modify(i);
for(int i=l;i<=r;++i) cur[A[i]]=query(A[i]);
int hl=0,hr=0;
for(int i=l;i<=r;++i)
{
if(A[i]<=mid) L[++hl]=A[i];
else if(cur[A[i]]^sta[A[i]]) L[++hl]=A[i];
else R[++hr]=A[i];
}
for(int i=le;i<=mid;++i) sta[i]^=1;
for(int i=l;i<=r;++i) sta[A[i]]=cur[A[i]];
for(int i=l;i<l+hl;++i) A[i]=L[i-l+1];
for(int i=l+hl;i<=r;++i) A[i]=R[i-l-hl+1];
Binary(le,mid,l,l+hl-1);Binary(mid+1,ri,l+hl,r);
}
void Solve()
{
for(int i=1;i<=n;++i) A[i]=i-1;
Binary(0,n-1,1,n);
}
}
namespace pt68
{
queue <int> q;map<pair<int,int>,bool> mp;
int S[MAXN];
void Solve()
{
int up=log2(n);
for(int k=0;k<=up;++k)
{
for(int i=0;i<n;++i)
if((1<<k)&i) modify(i);
for(int i=0;i<n;++i)
S[i]|=(query(i)<<k);
for(int i=0;i<n;++i)
if((1<<k)&i) modify(i);
}
for(int i=0;i<n;++i) S[i]^=i;
for(int i=0;i<n;++i) q.push(i);
while(!q.empty())
{
int p=q.front();
q.pop();
if(S[p]>=n) continue;
modify(S[p]);
int cur=query(p);
modify(S[p]);
if(cur&&p!=S[p])
{
int u=p,v=S[p];
if(u>v) swap(u,v);
if(mp[make_pair(u,v)]) continue;
mp[make_pair(u,v)]=1;
report(p,S[p]);
q.push(S[p]);q.push(p);
S[S[p]]^=p;
S[p]=0;
}
}
}
}
namespace pt100
{
map<pair<int,int>,bool> mp;
vector <int> e[MAXN];
int sta[MAXN],cur[MAXN],pos[MAXN],tot;
int A[MAXN],L[MAXN],R[MAXN],id[MAXN];
bool emp[MAXN];
void Binary(int le,int ri,int l,int r)
{
if(l>r||le>ri) return ;
if(le==ri)
{
for(int i=l;i<=r;++i)
{
int u=A[i],v=id[le];if(u>v) swap(u,v);
if(A[i]!=id[le]&&!mp[make_pair(u,v)])
{
report(A[i],id[le]);mp[make_pair(u,v)]=1;
emp[A[i]]=check(A[i]);emp[id[le]]=check(id[le]);
e[A[i]].push_back(id[le]);e[id[le]].push_back(A[i]);
}
}
return ;
}
int mid=le+ri>>1;
for(int i=le;i<=mid;++i)
{
modify(id[i]);
for(int v:e[id[i]]) sta[v]^=1;
}
for(int i=l;i<=r;++i) cur[A[i]]=query(A[i]);
int hl=0,hr=0;
for(int i=l;i<=r;++i)
{
if(pos[A[i]]<=mid) L[++hl]=A[i];
else if(cur[A[i]]^sta[A[i]]) L[++hl]=A[i];
else R[++hr]=A[i];
}
for(int i=le;i<=mid;++i)
{
modify(id[i]);
for(int v:e[id[i]]) sta[v]^=1;
}
for(int i=l;i<l+hl;++i) A[i]=L[i-l+1];
for(int i=l+hl;i<=r;++i) A[i]=R[i-l-hl+1];
Binary(le,mid,l,l+hl-1);Binary(mid+1,ri,l+hl,r);
}
void Solve()
{
tot=0;
for(int i=0;i<n;++i) id[++tot]=i;
while(tot)
{
srand(time(NULL));
random_shuffle(id+1,id+1+tot);
for(int i=1;i<=tot;++i) A[i]=id[i];
for(int i=1;i<=tot;++i) pos[id[i]]=i;
Binary(1,tot,1,tot);
tot=0;
for(int i=0;i<n;++i) if(!emp[i]) id[++tot]=i;
}
}
}
void explore(int N, int M)
{
n=N;m=M;
if(N<=500) pt20::Solve();
else if(M==N/2&&N%10==8) pt36::Solve();
else if(N%10==7) pt44::Solve();
else if(N%10==6||N%10==5)pt68::Solve();
else pt100::Solve();
}