花了4個小時終於搞懂了這題……膜拜現場各種1h以內a掉的julao。
這道題全場大概30幾人ac,難度不算太高也不算太低,算是d2最簡單,全場第二簡單的題。
你要構造一張圖,其中起點編號爲0,有m個給定的普通點,編號爲1~m,你還可以添加若干個特殊點(數量自己決定,這裏設爲s個),編號-1~-s。所有點可以有任意多條入邊,但起點普通點只能有1條出邊,特殊點只能有2條出邊。
每個特殊點滿足:當第奇數次進入它時,會從第1條出邊走出,否則會從第2條出邊走出。
要求你從起點出發,一路上一共n次經過普通點(每個普通點可以經過多次,經過的順序是給定的),同時經過每個特殊點都是偶數次,然後返回起點(第一次返回起點後即停止)。
你最多隻能使用個特殊節點。
相信大家第一眼見到這道題都會毫無頭緒,畢竟構造題往往是需要開一番腦洞的,尤其這還是ioi的題,更是需要“天才級的想象力”。別急,讓我們一步一步地來。
以下用圓點代表起點,方點代表普通點,三角代表特殊點,特殊點的奇偶出邊分別用x和y表示。
首先我們肯定會對一些小規模或特殊數據,想一些特殊結構。比如:
只經過1次1的很簡單:
經過2次1也很簡單:
稍微想一下,我們就能用2個特殊點構造出經過4次1的情況:
實際上這種方法可以任意擴展到經過次1號節點,只需要個特殊點:
然而我們陷入了困境,一是因爲這種方法難以擴展到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序,因此難以找到一整棵可以直接刪除的子樹,導致我們無法節省節點。
那麼我們一開始就直接按照中點分治?也不行。還是由於前述原因(也是由於要經過偶數次的原因),分治的兩棵子樹必須一樣大,因此我們可能需要在一側補一條指向入點的出邊,最終算下來還是無法節約節點。
那怎麼辦?
此時我們要注意到特殊點數量限制是。這個log從何而來?回顧前面的解題過程,我們發現前面恰好有一個需要個特殊點的結構。
分析之,我們發現:設結構大小爲k,則第2條x邊會經過次,第2條x邊會經過次……第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);
}