[IOI2018]-day1 簡要題解

VictoryCzt發表於2019-01-03

因為只有4種字母,且只能詢問n+2n+2次,那麼我們需要平均一次就要確定一位。

而詢問的串長,最長為4×n4\times n,所以每次對於一個要確定的字母我們可以直接輸出四種情況。但是這樣需要2次確定一位,所以我們需要進一步優化。

首先,我們可以花兩步確定第一位:

詢問"AB",如果返回的值大於0,則就是"AB"中的一個,然後就輸出一個"A",如果返回值為1,那麼開頭就是"A",否則為"B"。如果不是"AB",那麼就是"XY",所以輸出"X",和"AB"同樣的判斷方法,那麼就可以知道第一位。

對於後面的每一位,我們要儘量做到一次詢問確定一位。由於開頭的那一位不會在後面再次出現,那麼我們只剩下三種選擇。

舉個例子說明:

假如開頭是"A",那麼下一位可能為"B,X,Y",而如果每次只增加一位的話,就還要花費一個操作去確定,所以我們增加兩位:“BB”,“BX”,“BY”,這樣如果返回值沒有增加,下一位就不是"B",但是如果增加了兩個,那麼下一位就是"B",如果不是"B"的話,還要判斷"X,Y",由於"A"不能再出現,所以我們再加一個"XA",這樣如果返回值只增加了一個,那麼下一位就是"X",否則沒增加就是"Y"。

所以直接模擬,而到了最後一位,直接按照開始的方法,在剩下三種情況中花費兩個操作判斷一下即可。

程式碼(互動):

#include "combo.h"
using namespace std;

string Nex(char a,int id){
	if(a=='A'){
		if(id==1) return "BB";
		if(id==2) return "BX";
		if(id==3) return "BY";
		if(id==4) return "XA";
	}else if(a=='B'){
		if(id==1) return "AA";
		if(id==2) return "AX";
		if(id==3) return "AY";
		if(id==4) return "XB";
	}else if(a=='X'){
		if(id==1) return "AA";
		if(id==2) return "AB";
		if(id==3) return "AY";
		if(id==4) return "BX";
	}else if(a=='Y'){
		if(id==1) return "AA";
		if(id==2) return "AB";
		if(id==3) return "AX";
		if(id==4) return "BY";		
	}
}
string Ok(char a,int con,int bef){
	if(a=='A'){
		if(con==bef){
			return "Y";
		}else if(con==bef+1){
			return "X";
		}else if(con==bef+2){
			return "B";
		}
	}else if(a=='B'){
		if(con==bef){
			return "Y";
		}else if(con==bef+1){
			return "X";
		}else if(con==bef+2){
			return "A";
		}		
	}else if(a=='X'){
		if(con==bef){
			return "Y";
		}else if(con==bef+1){
			return "B";
		}else if(con==bef+2){
			return "A";
		}		
	}else if(a=='Y'){
		if(con==bef){
			return "X";
		}else if(con==bef+1){
			return "B";
		}else if(con==bef+2){
			return "A";
		}		
	}
}
string End(char a,int id){
	if(a=='A'){
		if(id==1){
			return "B";
		}else if(id==2){
			return "X";
		}else if(id==3){
			return "Y";
		}
	}else if(a=='B'){
		if(id==1){
			return "A";
		}else if(id==2){
			return "X";
		}else if(id==3){
			return "Y";
		}		
	}else if(a=='X'){
		if(id==1){
			return "A";
		}else if(id==2){
			return "B";
		}else if(id==3){
			return "Y";
		}		
	}else if(a=='Y'){
		if(id==1){
			return "A";
		}else if(id==2){
			return "B";
		}else if(id==3){
			return "X";
		}				
	}
}
string guess_sequence(int N){
	string st="";
	string pre="AB";
	int con=press(pre);
	if(!con){
		con=press("X");
		if(con) st="X";
		else st="Y";
	}else{
		con=press("A");
		if(con) st="A";
		else st="B";
	}
	pre=st;
	if(N==1) return pre;
	for(int i=2;i<=N-1;i++){
		string ask="";
		for(int j=1;j<=4;j++)ask+=pre+Nex(st[0],j);
		con=press(ask);
		pre+=Ok(st[0],con,i-1);
	}
	string last=pre+End(st[0],1)+pre+End(st[0],2);
	con=press(last);
	if(con==N-1){
		last=pre+End(st[0],3);
	}else{
		last=pre+End(st[0],1);
		con=press(last);
		if(con==N-1){
			last=pre+End(st[0],2);
		}
	}
    return last;
}

開始我想的是,對於一個字首0L0\sim L,我們維護裡面最左下角的點和最右上角的點,然後每次詢問我們就判斷每個字首數的個數是不是等於所在矩形區間的大小,為了支援快速查詢修改,外面再套一個樹,或者用主席樹之類的,但是就是十分複雜,且複雜度為O(nlog2n)O(nlog^2n)的,是無法通過所有資料的。

所以我們轉換思路,對於一個字首0L0\sim L如果它形成了一個矩形,那麼與其相交的所有2×22\times 2的子矩形要麼是隻有兩個相交,要麼是全部在裡面,要麼是隻有一個相交,而一個的只有矩形的四個頂點,而三個相交的又不存在,但是2,4個的個數不確定,所以我們就只維護對於一個字首0L0\sim L的所在格子形成的圖形與其相交的所有2×22\times 2的子矩形中1,31,3的個數,如果合法的話必須只有4,04,0個。

這個可以先預處理出來,然後用權值線段樹維護即可,每次修改就交換然後線段樹上改這兩個點周圍包含它的2×22\times 2的子矩形即可,複雜度O(nlogn)O(nlogn)

#include "seats.h"

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1e6+10;

vector <int> mp[M];
int rr[M],cc[M];
int B1[M],B3[M];
int n,m;
struct node{
	int b1,b3;
	node(){}
	node(int a,int b):b1(a),b3(b){}
	bool operator <(const node &a)const{
		return b1<a.b1||(b1==a.b1&&b3<a.b3);
	}
	node operator +(const node &a)const{
		return node(b1+a.b1,b3+a.b3);	
	}
	bool operator ==(const node &a)const{
		return b1==a.b1&&b3==a.b3;	
	}
	node operator +=(const node &a){return *this=*this+a;}
	bool isok(){return b1==4&&!b3;}
};

int tot,root;
node S[M<<2],lazy[M<<2];
int ls[M<<2],rs[M<<2],sum[M<<2];

void pushup(int o){
	S[o]=min(S[ls[o]],S[rs[o]]);sum[o]=0;
	if(S[o]==S[ls[o]])sum[o]+=sum[ls[o]];
	if(S[o]==S[rs[o]])sum[o]+=sum[rs[o]];
}
void pushdown(int o){
	node &now=lazy[o];
	if(now==node(0,0)) return;
	S[ls[o]]+=now;S[rs[o]]+=now;
	lazy[ls[o]]+=now;lazy[rs[o]]+=now;
	now=node(0,0);
}
void build(int &o,int l,int r){
	o=++tot;lazy[o]=node(0,0);
	if(l==r){
		S[o]=node(B1[l],B3[l]);sum[o]=1;
		return;
	}
	int mid=l+r>>1;
	build(ls[o],l,mid);
	build(rs[o],mid+1,r);
	pushup(o);
}
void update(int o,int l,int r,int L,int R,node v){
	if(L>R) return;
	if(L<=l&&r<=R){
		S[o]+=v;lazy[o]+=v;
		return;
	}
	pushdown(o);
	int mid=l+r>>1;
	if(L<=mid) update(ls[o],l,mid,L,R,v);
	if(R>mid) update(rs[o],mid+1,r,L,R,v);
	pushup(o);
}
int query(){
	if(S[root].isok()) return sum[root];
	else return 0;
}
int stk[4];
const int dx[]={0,1,0,1},dy[]={0,0,1,1};
void init(int x,int y){
	for(int i=0;i<=3;i++){
		stk[i]=mp[x+dx[i]][y+dy[i]];
	}	sort(stk,stk+4);
}
void give_initial_chart(int H, int W, std::vector<int> R, std::vector<int> C) {
	n=H,m=W;
	for(int i=0;i<=n+1;i++){
		mp[i].resize(m+2);
		for(int j=0;j<=m+1;j++){
			mp[i][j]=n*m+1;
		}
	}
	for(int i=1,sz=n*m;i<=sz;i++){
		rr[i]=R[i-1]+1;cc[i]=C[i-1]+1;
		mp[rr[i]][cc[i]]=i;
	}
	for(int i=0;i<=n;i++)for(int j=0;j<=m;j++){
		init(i,j);
		++B1[stk[0]];--B1[stk[1]];
		++B3[stk[2]];--B3[stk[3]];
	}
	for(int i=1,sz=n*m;i<=sz;i++){
		B1[i]+=B1[i-1];
		B3[i]+=B3[i-1];
	}
	build(root,1,n*m);
}
void New(int x,int y,int v){
	init(x,y);
	update(root,1,n*m,stk[0],stk[1]-1,node(v,0));
	update(root,1,n*m,stk[2],stk[3]-1,node(0,v));
}
const int tx[]={0,-1,0,-1},ty[]={0,0,-1,-1};
void Up(int x,int y,int v){
	for(int i=0;i<=3;i++)
		New(x+tx[i],y+ty[i],-1);
	mp[x][y]=v;
	for(int i=0;i<=3;i++)
		New(x+tx[i],y+ty[i],1);
}
int swap_seats(int a, int b) {
	++a;++b;
	int v1=mp[rr[a]][cc[a]],v2=mp[rr[b]][cc[b]];
	swap(rr[a],rr[b]);swap(cc[a],cc[b]);
	Up(rr[a],cc[a],v1);
	Up(rr[b],cc[b],v2);
	return query();
}

由於每次每種形態不能去和能去的都是一個編號的字首或者字尾,所以我們考慮將圖轉換成兩個重構樹,一個是編號小的向大的,另一個是編號大的向小的。

然後一個字首就對應了小的向大的樹上的一個子樹,字尾就對應了大的向小的上面的一個子樹,每次用類似二維數點的方式判斷這兩個子樹集合是否有交,有交就證明可以在這裡變成狼人到達終點,否則就不行。

將詢問離線,在兩個樹上的dfs序上,就可以用樹狀陣列來求答案啦。

這裡的重構樹的方法類似於kruskal重構樹,所以用個並查集來建就好了。

#include "werewolf.h"

#include<cstdio>
#include<cstring>
#include<algorithm> 
#define lowbit(a) ((a)&(-(a)))
using namespace std;
const int M=1e6+1; 
const int Log=20;
struct DSU{
	int f[M];
	void init(int n){
		for(int i=0;i<=n;i++)f[i]=i;
	}
	int find(int a){return f[a]==a?a:f[a]=find(f[a]);}
	int & operator [](int a){return f[a];}
	int operator ()(int a){return find(a);}
};
int n;
struct Rebuild_Tree{
	vector <int> g[M],t[M];
	int in[M],root;
	void add(int a,int b){g[a].push_back(b);}
	void link(int a,int b){t[a].push_back(b);++in[b];}
	DSU F;
	int dfn[M],out[M],tim;
	int f[Log][M];
	void dfs(int a){
		dfn[a]=++tim;
		for(int i=1;i<Log;i++)
			f[i][a]=f[i-1][f[i-1][a]];
		for(auto v: t[a]){
			f[0][v]=a;
			dfs(v);
		}
		out[a]=tim;
	}
	void build(bool flag){
		F.init(n);
		if(flag){
			for(int i=1,u;i<=n;i++){
				for(auto v: g[i]){
					u=F(v);
					if(i!=u){
						F[u]=i;
						link(i,u);
					}
				}
			}
		}else{
			for(int i=n,u;i>=1;i--){
				for(auto v: g[i]){
					u=F(v);
					if(i!=u){
						F[u]=i;
						link(i,u);
					}
				}				
			}
		}
		for(int i=1;i<=n;i++)if(!in[i]){root=i;break;}
		dfs(root);
	}
	void Find(int a,int to,int tp,int &x,int &y){
		for(int i=Log-1;i>=0;i--){
			if(f[i][a]&&(tp?f[i][a]>=to:f[i][a]<=to)){
				a=f[i][a];
			}
		}
		x=dfn[a]-1;
		y=out[a];
	}
}T1,T2;
int bit[M];
void add(int a,int b){
	for(;a<=n;a+=lowbit(a))bit[a]+=b;
}
int query(int a){
	int res=0;
	for(;a;a-=lowbit(a))res+=bit[a];
	return res;
}
struct Ask{
	int s,t;
	Ask(){}
	Ask(int a,int b):s(a),t(b){}
};
vector <Ask> Q[M];
vector <int> ans;
int ref[M];
int a,b;
vector<int> check_validity(int N,vector<int> X,vector<int> Y,vector<int> S,vector<int> E,vector<int> L,vector<int> R) {
	n=N;
	for(int i=0,sz=X.size();i<sz;i++){
		a=X[i]+1;b=Y[i]+1;
		if(a<b)swap(a,b);
		T1.add(a,b);T2.add(b,a);
	}
	T1.build(1);
	T2.build(0);
	for(int i=0,sz=S.size(),lx,rx,ly,ry;i<sz;i++){
		T2.Find(S[i]+1,L[i]+1,1,lx,rx);
		T1.Find(E[i]+1,R[i]+1,0,ly,ry);
		Q[lx].push_back(Ask(ly,i));Q[lx].push_back(Ask(-ry,i));
		Q[rx].push_back(Ask(-ly,i));Q[rx].push_back(Ask(ry,i));
	}
	for(int i=1;i<=n;i++)
		ref[T2.dfn[i]]=T1.dfn[i];
	ans.resize(S.size());
	for(int i=1;i<=n;i++){
		add(ref[i],1);
		for(int j=0,sz=Q[i].size();j<sz;j++){
			Ask now=Q[i][j];
			int fl=now.s<0?now.s=-now.s,-1:1;
			ans[now.t]+=query(now.s)*fl;
		}
	}	
	for(int i=0,sz=ans.size();i<sz;i++)if(ans[i]>0)ans[i]=1;else ans[i]=0;
	return ans;
}