【LOJ #3161】「NOI2019」I 君的探险(随机化+整体二分)

传送门

首先是 68 68 68分对于树的做法(和正解没有关系)
点编号为 1 − n 1-n 1n
考虑对于每一个二进制位 d d d
把这编号位为1的点 m o d i f y modify modify
然后对每个点 q u e r y query query
如果颜色改变,那么 s u m + = 1 < < d sum+=1<sum+=1<<d
然后可以发现这样求出来每一个点相连点的编号异或和

然后考虑一个剥叶子的过程
对于每一个点
s u m [ u ]   m o d i f y sum[u]\ modify sum[u] modify
如果 u u u颜色改变
那么 u u u之和 s u m [ u ] sum[u] sum[u]相连
于是就可以把这条边断开
继续对 s u m [ u ] sum[u] sum[u]操作

可以发现对于树一定可以把所有边找出来
复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
不过有些特殊判断

( 代 码 n a m e s p a c e   s o l v e 2 ) (代码namespace\ solve2) (namespace solve2)


考虑对于一个点 i i i
1 , n 1,n 1,n所有点除了 i i i m o d i f y modify modify
如果其颜色改变
那么一定连了奇数条边出去
然后对于 1 , m i d 1,mid 1,mid进行 m o d i f y modify modify
如果颜色也改变
那么必定有奇数条边连向 1 , m i d 1,mid 1,mid
于是继续做

对一个点是二分
于是可以对所有点整体二分
m o d i f y [ l , m i d ] , q u e r y ( m i d + 1 , r ) modify[l,mid],query(mid+1,r) modify[l,mid],query(mid+1,r)
然后把询问挂到 l , m i d l,mid l,mid
题解说 可以证明随机情况下期望有 n / 3 n/3 n/3的点像前连了奇数条边
于是可以 r a n d rand rand一个排列这样做
做完之后把所有孤立点 c h e c k check check出除

复杂度 m l o g n mlogn mlogn

#include "explore.h"
#include
using namespace std;
#define cs const
#define re register
#define pii pair
#define fi first
#define se second
#define ll long long
#define pb push_back
cs int RLEN=1<<20|1;
inline char gc(){
	static char ibuf[RLEN],*ib,*ob;
	(ib==ob)&&(ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
	return (ib==ob)?EOF:*ib++;
}
inline int read(){
	char ch=gc();
	int res=0;bool f=1;
	while(!isdigit(ch))f^=ch=='-',ch=gc();
	while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=gc();
	return f?res:-res;
}
inline int readstring(char *s){
	int top=0;
	char ch=gc();
	while(isspace(ch))ch=gc();
	while(!isspace(ch)&&ch!=EOF)s[++top]=ch,ch=gc();
	return top;
}
template<class tp>inline void chemx(tp &a,tp b){a<b?a=b:0;}
template<class tp>inline void chemn(tp &a,tp b){a>b?a=b:0;}
cs int N=300005;
int n,m;
namespace solve1{
	int sta[N];
	inline void main(){
		for(int i=0;i<n-1;i++){
			modify(i);
			for(int j=i+1;j<n;j++){
				int now=query(j);
				if(sta[j]!=now)report(i,j);
				sta[j]=now;
			}
		}
	}
}
namespace solve2{
	int sta[N],sum[N];
	queue<int> q;
	map<pii,bool>vt;
	inline void main(){
		for(int i=0;(1<<i)<=n;i++){
			for(int j=1;j<=n;j++)if(j&(1<<i)){
				modify(j-1);
			}
			for(int j=1;j<=n;j++){
				if(query(j-1)!=sta[j]){
					sum[j]+=1<<i,sta[j]^=1;
				}
			}
		}
		for(int i=1;i<=n;i++)sum[i]^=i;
		if(n%10>=7){
			for(int i=n;i;i--)if(sum[i]){
				report(i-1,sum[i]-1),sum[sum[i]]^=i;
			}
			return;
		}
		for(int i=1;i<=n;i++)q.push(i);
		while(!q.empty()&&m){
			int u=q.front();q.pop();
			if(sum[u]>0&&sum[u]<=n&&sum[u]!=u&&vt.find(pii(min(sum[u],u),max(sum[u],u)))==vt.end()){
				int pre=query(sum[u]-1);
				modify(u-1);
				if(query(sum[u]-1)!=pre){
					report(sum[u]-1,u-1),m--;
					q.push(sum[u]),vt[pii(min(sum[u],u),max(sum[u],u))]=1;
					sum[sum[u]]^=u,sum[u]=0;
				}
			}
		}
	}
}
namespace solve3{
	vector<int> q[N<<2];
	vector<int> e[N];
	int p[N],sta[N];
	#define lc (u<<1)
	#define rc ((u<<1)|1)
	#define mid ((l+r)>>1)
	void solve(int u,int l,int r){
		if(l==r){
			for(int &x:q[u]){
				report(x,p[l]);
				e[p[l]].pb(x),e[x].pb(p[l]);
			}
			return;
		}
		for(int i=l;i<=mid;i++){
			modify(p[i]);
			for(int &v:e[p[i]])sta[v]^=1;
		}
		for(int i=mid+1;i<=r;i++){
			if(query(p[i])!=sta[p[i]])
			q[lc].pb(p[i]);
		}
		for(int &x:q[u]){
			if(query(x)!=sta[x])
			q[lc].pb(x);
			else q[rc].pb(x);
		}
		for(int i=l;i<=mid;i++){
			modify(p[i]);
			for(int &v:e[p[i]])sta[v]^=1;
		}
		solve(lc,l,mid),solve(rc,mid+1,r);
	}
	#undef lc
	#undef rc
	#undef mid
	inline void main(){
		srand(0721);
		for(int i=0;i<n;i++)p[i+1]=i;
		while(n){
			random_shuffle(p+1,p+n+1);
			for(int i=1;i<=n;i++)sta[p[i]]=query(p[i]);
			solve(1,1,n);
			for(int i=1;i<=n*4;i++)q[i].clear();
			int nn=0;
			for(int i=1;i<=n;i++)if(!check(p[i]))p[++nn]=p[i];
			n=nn;
		}
	}
}
void explore(int _N, int _M) {
	n=_N,m=_M;
	if(n<=500)solve1::main();
	else if(n>m)solve2::main();
	else solve3::main();
}

你可能感兴趣的:(整体二分,二分答案)