「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();
}
posted @ 2022-04-27 14:11  夜空之星  阅读(105)  评论(0编辑  收藏  举报