CF1053E Euler Tour

Address

Solution

  • 可以發現,一個歐拉序 aa 合法當且僅當:

    1. 1i2n1,1ain\forall 1 \le i \le 2n - 1, 1 \le a_i \le n
    2. a1=a2n1a_1 = a_{2n - 1}
    3. al=ar\forall a_l = a_rrlr - l 爲偶數且 [l,r][l,r] 內出現了恰好 rl2+1\dfrac{r - l}{2} + 1 種不同的權值;
    4. 所有滿足 al=ara_l = a_r 的區間 [l,r][l,r] 只有包含關係。
  • 設當前區間爲 [l,r][l,r],因爲滿足 al=ara_l' = a_r' 的區間 [l,r][l',r'] 一定代表一棵子樹,可以遞歸處理 (l,r)(l',r'),然後將 [l,r][l',r'] 縮成一個點 ala_{l'}

  • 對於每個 ii 預處理下一個和它權值相同的位置,就可以在縮點時順便判斷一下條件 44

  • 現在 [l,r][l,r] 中就不存在權值相同的點,如果區間中出現了少於 rl2+1\frac{r - l}{2} + 1 種權值,就將還未出現的權值填在區間的最前面(若權值種類數已經超過 rl2+1\frac{r - l}{2} + 1 則無解), 之後從左往右考慮 [l,r][l,r] 中每個長度爲 33 的區間,若存在一個長度爲 33 的區間爲 xy00yx 的形式,我們就可以將其補成 xyx,然後再縮成一個點 x

  • 將所有能夠縮的區間縮完之後可能還會有位置爲 00,我們可以將剩下的位置都填成這棵子樹的根。

  • 考慮這樣做的正確性:

    1. 若不存在兩個不爲 00 的位置相鄰,區間的首尾一定已經填入數字且相鄰的兩個已經填入數字的位置中恰好隔着一個 00,此時將剩下的位置填成這棵子樹的根顯然正確;
    2. 若存在兩個不爲 00 的位置相鄰,縮點之後不爲 00 的位置減少了 11,爲 00 的位置也減少了 11,兩者的差不變,因此可以看做是遞歸下去,直到出現第 11 種情況。
  • 特別地,對於 [1,2n1][1, 2n - 1] 我們需要保證首尾相同,進行一些簡單的討論:

    1. a1,a2n1>0a_1, a_{2n - 1} > 0a1a2n1a_1 \neq a_{2n - 1},無解;
    2. a1,a2n1>0a_1, a_{2n - 1} > 0a1=a2n1a_1 = a_{2n - 1},遞歸區間 (1,2n1)(1, 2n - 1)
    3. a1>0a_1 > 0a2n1>0a_{2n - 1} > 0,將兩個數填成相同的,遞歸區間 (1,2n1)(1, 2n - 1)
    4. a1=a2n1=0a_1 = a_{2n - 1} = 0,直接按之前算法處理即可,從左到右合併能保證最後首尾相同且沒有剩餘位置爲 00
  • 可以用鏈表實現,時間複雜度 O(n)\mathcal O(n),期望得分 100pts\text{100pts}

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch;
	while (ch = getchar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
}

template <class T>
inline void put(T x)
{
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

const int N = 1e6 + 5;
int a[N], vis[N], nxt[N], pre[N], suf[N];
int n, m, now = 1;

inline void Fail()
{
	puts("no");	
	exit(0);
}

inline int Get()
{
	while (vis[now])
		++now;
	if (now > n)
		Fail();
	vis[now] = 1;
	return now;
}

inline void Delete(int l, int r)
{
	int tl = pre[l], tr = suf[r];
	suf[tl] = tr, pre[tr] = tl;
}

inline void Merge(int l, int r, int &x)
{
	while (x > l && suf[suf[x]] && suf[suf[x]] <= r && !a[x] && a[suf[x]] && a[suf[suf[x]]])
	{
		a[x] = a[suf[suf[x]]];
		Delete(suf[x], suf[suf[x]]);
		x = pre[pre[x]];
	}
	while (x > l && suf[suf[x]] && suf[suf[x]] <= r && a[x] && a[suf[x]] && !a[suf[suf[x]]])
	{
		a[suf[suf[x]]] = a[x];
		Delete(suf[x], suf[suf[x]]);
		x = pre[pre[x]];
	}
}

inline void solve(int l, int r)
{
	if (r - l & 1)
		Fail();
	for (int i = l; i <= r; i = suf[i])
		while (nxt[i])
		{
			if (nxt[i] > r)
				Fail();
			solve(suf[i], pre[nxt[i]]);
			Delete(suf[i], nxt[i]);
			nxt[i] = nxt[nxt[i]];
		}
	int num1 = 0, num2 = 0, rt = a[pre[l]];
	for (int i = l; i <= r; i = suf[i])
		num1 += a[i] > 0, ++num2;
	num2 = (num2 >> 1) + 1;
	if (num1 > num2)
		Fail();
	num2 -= num1; 
	for (int i = l; num2 && i <= r; i = suf[i])
		if (!a[i])
			a[i] = Get(), --num2;	
	for (int i = l; i <= r; i = suf[i])
		Merge(l - 1, r, i);
	for (int i = l; i <= r; i = suf[i])
		if (!a[i])
			a[i] = rt;
	
}

int main()
{
	read(n); 
	if (n == 1)
		return puts("yes\n1"), 0;
	m = (n << 1) - 1;
	for (int i = 1; i <= m; ++i)
		read(a[i]);
	for (int i = 2; i <= m; ++i)
		if (a[i] && a[i - 1] && a[i] == a[i - 1])
			Fail();
	if (a[1] && a[m] && a[1] != a[m])
		Fail();
	a[1] = a[m] = a[1] | a[m];
	for (int i = 0; i <= m + 1; ++i)
		pre[i] = i - 1, suf[i] = i + 1;
	pre[0] = 0;
	for (int i = m; i >= 1; --i)
		if (a[i])
		{
			nxt[i] = vis[a[i]];
			vis[a[i]] = i;
		}
	solve(1, m);
	puts("yes");
	for (int i = 1; i <= m; ++i)
		put(a[i]), putchar(' ');
	return 0;
}

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