1. 程式人生 > 實用技巧 >luogu P5473 [NOI2019]I 君的探險 互動 隨機 二分 分治 整體二分

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;
}

也同時從中學到了一些互動除錯的技巧。