[ioi2018 d2t1] doll - 有趣的構造題

花了4個小時終於搞懂了這題……膜拜現場各種1h以內a掉的julao。

這道題全場大概30幾人ac,難度不算太高也不算太低,算是d2最簡單,全場第二簡單的題。

你要構造一張圖,其中起點編號爲0,有m個給定的普通點,編號爲1~m,你還可以添加若干個特殊點(數量自己決定,這裏設爲s個),編號-1~-s。所有點可以有任意多條入邊,但起點普通點只能有1條出邊,特殊點只能有2條出邊。

每個特殊點滿足:當第奇數次進入它時,會從第1條出邊走出,否則會從第2條出邊走出。

要求你從起點出發,一路上一共n次經過普通點(每個普通點可以經過多次,經過的順序是給定的),同時經過每個特殊點都是偶數次,然後返回起點(第一次返回起點後即停止)。

你最多隻能使用n+\log_{2}n個特殊節點。

相信大家第一眼見到這道題都會毫無頭緒,畢竟構造題往往是需要開一番腦洞的,尤其這還是ioi的題,更是需要“天才級的想象力”。別急,讓我們一步一步地來。

以下用圓點代表起點,方點代表普通點,三角代表特殊點,特殊點的奇偶出邊分別用x和y表示。

首先我們肯定會對一些小規模或特殊數據,想一些特殊結構。比如:

只經過1次1的很簡單:

經過2次1也很簡單:

稍微想一下,我們就能用2個特殊點構造出經過4次1的情況:

實際上這種方法可以任意擴展到經過2^{_{n}}次1號節點,只需要\log_{2}n個特殊點:

然而我們陷入了困境,一是因爲這種方法難以擴展到n不是2的冪,二是如果經過的節點並非全1,我們也沒有好的方法。

實際上,由於經由序列多種多樣,直接分析每個經由序列構造方案的性質來構造幾乎不可能,因此我們要想一個通用的方法。

此時,“特殊點”的性質給了我很大的啓發——既然特殊點是一個有2條出邊的結構,那我們能否構造一個由特殊點組成的結構,它有一個入點和k條出邊,且以k爲循環地依次從每條出邊離開?

這個結構可以看做一個“放大版特殊點”。下文中,我們稱這種結構爲“k開關”。一個特殊點也可以看做“2開關”。

我們發現,如果我們能對任意k實現k開關,問題就直接解決了——我們構造一個n開關,將起點連向第一個經過的普通點,所有普通點連向開關的入點,開關的前n-1條出路分別連向序列中的普通點,最後一個連向起點。

由於一個k開關在經過k次後,所有特殊點必然經過偶數次(不然要麼有無用特殊點可以刪除,要麼無法實現以k爲大小的循環),因此這個結構可以直接解決問題。

問題變成了k開關的實現。對線段樹一類的數據結構比較熟悉的人容易想到一個構造方案:

如圖,一個類似於滿二叉樹的構型。

可惜這是對於k是2的冪的情況而言。那麼如果k不是2的冪呢?

一種方法是將最後一條出邊之前的若干條出邊直接連回入點,比如下圖這個k=3的結構:

如圖,要注意連向入點的出邊其實是第3次經過的出邊。

然而這樣我們就需要將節點數補到>=k的一個2的冪,當k=2的冪+1時大約需要2k個節點,無法滿足要求。

我們能直接刪去一些最後若干次經過的、無用的節點嗎?不能。由於在結構中行進的順序並非簡單的dfs序,因此難以找到一整棵可以直接刪除的子樹,導致我們無法節省節點。

那麼我們一開始就直接按照中點分治?也不行。還是由於前述原因(也是由於要經過偶數次的原因),分治的兩棵子樹必須一樣大,因此我們可能需要在一側補一條指向入點的出邊,最終算下來還是無法節約節點。

那怎麼辦?

此時我們要注意到特殊點數量限制是n+\log_{2}n。這個log從何而來?回顧前面的解題過程,我們發現前面恰好有一個需要\log_{2}n個特殊點的結構。

分析之,我們發現:設結構大小爲k,則第2條x邊會經過2^{k-1}次,第2條x邊會經過2^{k-2}次……第k條x邊和y邊會經過1次。

如果我們將最後的y連出去(實際上是作爲最後的出邊連向起點),剩下的結構恰好支持我們對k-1進行二進制拆分!

具體而言,對k-1中爲1的位,我們接一個位權大小的滿二叉樹結構;對於0的位,直接連回入點即可。如下面k-1=21的結構:

下方大三角是形如2的冪的滿二叉樹結構,數字指出邊數量。

容易檢驗這個結構是符合上述所有要求(比如每個特殊點經過偶數次等)的。

如此我們就實現了k開關的設計,然後只需一次dfs就可以找到所有的出邊對應的次序,連接上序列中對應的普通點。再將起點連向第一個經過的普通點,將所有普通點連向開關的入點,將最後的y邊連向起點,整個結構便構建完畢。

於是這道題終於大功告成!感謝還有耐心看到這裏的你。

//以下代碼根據ioi提交格式編寫

#include<bits/stdc++.h>
using namespace std;
#include"doll.h"
int n,m;
vector<int> a,c,x,y;
int _x[400010],_y[400010],cnt,tot,rt = 1;
bool fg[400010];
void wk1(int &q,int l,int r){
	if(l == r) return;
	q = ++cnt;
	int mid = l + r >> 1;
	wk1(_x[q],l,mid);
	wk1(_y[q],mid + 1,r);
}
void create_circuit(int _m,vector<int> _a){
	m = _m;a = _a;n = a.size();
	if(n == 1){
		c.push_back(a[0]);
		for(int i = 1;i <= m;++i) c.push_back(0);
		answer(c,x,y);return;
	}
	int i,j,k,nw = 0,nxt;
	c.resize(m + 1);c[0] = a[0];for(i = 1;i <= m;++i) c[i] = -1;--n;
	for(j = 1,k = 0;j <= n;j <<= 1,++k);
	j >>= 1;k = n;
	while(j){
		++cnt;
		if(nw) _y[nw] = cnt;
		nw = cnt;
		if(k & j) wk1(_x[nw],1,j);
		else _x[nw] = -987654321;
		j >>= 1;
	}
	_y[nw] = 987654321;
	nw = 1;
	do{
		fg[nw] = !fg[nw];
		nxt = fg[nw] ? _x[nw] : _y[nw];
		if(!nxt){
			if(fg[nw]) _x[nw] = -a[++tot];
			else _y[nw] = -a[++tot];
			nw = 1;
		}
		else if(nxt < 0) nw = 1;
		else nw = nxt;
	}while(tot < n);
	x.resize(cnt);y.resize(cnt);
	for(i = 1;i <= cnt;++i){
		x[i - 1] = _x[i] == 987654321 ? 0 : _x[i] == -987654321 ? -1 : -_x[i];
		y[i - 1] = _y[i] == 987654321 ? 0 : _y[i] == -987654321 ? -1 : -_y[i];
	} 
	answer(c,x,y);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章