luogu P5473 [NOI2019]I 君的探险 交互 随机 二分 分治 整体二分

LINK:I 君的探险

神仙题!

考虑一个暴力的做法 每次点亮一个点 询问全部点 这样询问次数为 \(\frac{n\cdot (n-1)}{2}\) 可以通过前5个点.

考虑都为A的部分分 发现一个点只会和另外一个点进行连边.

且询问次数要求\(nlogn\) 需要分治 二分等方法。

一个想法是 每次点亮一个再询问全部太浪费了 可以进行分治.

即每次点亮\(\frac{1}{4}\)数量的点 然后观察 如果两个点是一组的那么他们的状态相同 按照状态来划分区域再进行分治下去.

每次可以rand选点 然后就可以通过A的部分了。询问次数不太清楚。。

这部分其实是有标算的 考虑二进制 枚举某个二进制位 将为1的都点亮 询问所有灯 可以发现 自己和之前的状态相同当且仅当和自己相连的灯的二进制位和自己相同这个可以直接的进行计算。询问次数\(nlogn\)

考虑为B的部分分 每个点的父亲都小于儿子的编号 容易想到二分 而单调性还不存在。

不过把0~mid的灯全点亮然后check当前 这个是存在单调性的。

逐一二分过不去 可以考虑整体二分 询问次数\(nlogn\)

然后C,D 都不太会写且跟正解没有多大关系。

正解是 强上整体二分 然后发现只能求出和前面连边为奇数的那些点的边.

每次求出一部分然后random_shuffle 利用check来进行合理剪枝即可。

确实玄学。。不过这个随机的思路也不太难想。

code
//#include "explore.h"
//#include "grader.cpp"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include<bits/stdc++.h>
#define ll long long
#define db double
#define INF 10000000000000000ll
#define inf 1000000000
#define ldb long double
#define pb push_back
#define put_(x) printf("%d ",x);
#define get(x) x=read()
#define gt(x) scanf("%d",&x)
#define gi(x) scanf("%lf",&x)
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(p,n,i) for(RE int i=p;i<=n;++i)
#define go(x) for(int i=lin[x],tn=ver[i];i;tn=ver[i=nex[i]])
#define fep(n,p,i) for(RE int i=n;i>=p;--i)
#define vep(p,n,i) for(RE int i=p;i<(int)n;++i)
#define pii pair<int,int>
#define mk make_pair
#define RE register
#define gf(x) scanf("%lf",&x)
#define pf(x) ((x)*(x))
#define uint unsigned long long
#define ui unsigned
#define EPS 1e-4
#define sq sqrt
#define S second
#define F first
#define mod 1000000007
using namespace std;
void modify(int x);
int query(int x);
void report(int x,int y);
int check(int x);
int n,m;
inline void sc(int x,int y){report(x,y);}
const int MAXN=200010;
vector<int>g[MAXN],s,res;
int vis[MAXN],ans[MAXN];
inline void solve(int l,int r,vector<int> f)
{
	if(l==r)
	{
		vep(0,f.size(),j)
		{
			sc(f[j],s[r]);
			g[f[j]].pb(s[r]);
			g[s[r]].pb(f[j]);
		}
		return;
	}
	int mid=(l+r)>>1;
	vector<int>ql,qr;
	rep(l,mid,i)
	{
		modify(s[i]);
		vep(0,g[s[i]].size(),j)vis[g[s[i]][j]]^=1;
	}
	rep(mid+1,r,i)if(vis[s[i]]^query(s[i]))ql.pb(s[i]);
	vep(0,f.size(),i)if(vis[f[i]]^query(f[i]))ql.pb(f[i]);
	else qr.pb(f[i]);
	rep(l,mid,i)
	{
		modify(s[i]);
		vep(0,g[s[i]].size(),j)vis[g[s[i]][j]]^=1;
	}
	solve(l,mid,ql);
	solve(mid+1,r,qr);
}
inline void solve1()
{
	int lim=1;
	while((1<<lim)<n)++lim;
	rep(0,lim-1,j)
	{
		vep(0,n,i)
		{
			if(i&(1<<(j)))modify(i);
		}
		vep(0,n,i)
		{
			int ww=query(i);
			if(ww!=vis[i])
			{
				vis[i]=ww;
				ans[i]|=(i&1<<(j))^(1<<(j));
			}
			else ans[i]|=(i&1<<(j));
		}
	}
	//cout<<lim<<endl;
	vep(0,n,i)
	{
		if(i<ans[i])sc(i,ans[i]);
		//cout<<ans[i]<<endl;
	}
}
inline void solve2()
{
	solve(0,s.size()-1,vector<int>());
}
void explore(int N, int M)
{
	n=N;m=M;
	if(n<=1000)
	{
		rep(0,N-2,i)
		{
			modify(i);
			rep(i+1,N-1,j)
			{
				int ww=query(j);
				if(vis[j]!=ww)
				{
					vis[j]=ww;
					sc(i,j);	
				}
			}
		}
		return;
	}
	if(m==n/2)
	{
		solve1();
		return;
	}
	vep(0,n,i)s.pb(i);
	srand(time(0));
	if(n==99997||n==199997)
	{
		solve2();
		return;
	}
	while(s.size())
	{
		random_shuffle(s.begin(),s.end());
		solve(0,s.size()-1,vector<int>());
		vep(0,s.size(),j)if(!check(s[j]))res.pb(s[j]);
		s=res;res.clear();
	}
	return;
}

也同时从中学到了一些交互调试的技巧。

posted @ 2020-07-24 12:03  chdy  阅读(166)  评论(0编辑  收藏  举报