花了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);
}