LCT模板 LCT題型大薈萃

以HDU4010爲例,寫了LCT相關的一些題目的做法

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 3e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
/*
【算法介紹】
所謂動態樹(LCT),從數據結構上講,其實只不過是splay的擴展。
ACM中的很多問題,都是基於一棵固定形態的樹。然而LCT的存在,使得就算樹形態發生變化,也可以解決很多詢問。
<1> 首先要明確,我們在內部是用splay實現LCT,而在splay中,每個點到根節點距離的期望爲log級別,這是LCT複雜度有保障的基礎
<2> 在LCT中我們會有很多條preferred path(最多n條,即每點都獨屬於一條preferred path)。
	對於每條preferred path,以深度爲排序關鍵字,便可以得到一棵splay(我們把這棵splay稱作auxiliary tree)
<3> 我們不需要特殊維護所有點所呈的森林結構,森林結構的所有信息都存在splay中
	splay內部的父子關係是由其位置排列的左右來決定的
	splay中的關鍵詞是是深度,所以對於任意一個點x,如果把其轉爲其所在splay的根,則ch[x][0]就是x的祖先,ch[x][1]就是x的子孫
	即我們需要注意:ch[x][0]並不一定是x的父節點,last(ch[x][0])纔是;ch[x][1]並不一定是x的子節點,first(ch[x][1])纔是。
	同樣,在splay中,fa[x]所指向的節點,並非一定是真實樹形態中x的父節點。
	總結一下,就是——在splay中,我們只有通過理清深度關係,纔可以明確得到所有點在真實樹形態中的父子關係。
	而splay外部,splay的根節點x中存的fa[x],在真實樹結構中,就恰好對應爲x節點的父節點。

【算法實現】
<1> access(x) 
該操作的功能是取出從x到真實樹根直接的所有節點(這些節點一個也不多一個也不少,設爲集合S)並鏈接在splay中
在該函數中,每個節點將沿着fa[]指針一直爬到其所在splay的根節點。
由之前的理論可知,在access()中,沿着fa[]的爬升過程中,不一定是在同一棵splay()中進行的,而可能需要鏈接起不同的splay。
因此每爬升到一個節點x,我們都對x做splay操作,使得ch[x][0]爲x的祖先,ch[x][1]爲x的子孫,把ch[x][1]丟掉,其都不屬於集合S
同時因爲不設計到在ch[x][1]子樹內任何一個節點的修改,所以我們需要使得fa[ch[x][1]]保持不變,
而只是在access的路徑中,鏈接ch[x][1]爲son(son爲從起點向上爬升來的暫時的到樹根的鏈)。
我們發現,如果x到root的路徑很長,我們這裏爬升的步數就可能很多,對應一個比較大的時間複雜度,也就是論文中所說的糟糕初勢能。
但是,因爲採用了splay壓縮路徑,使得複雜度均攤爲log級別。通常,我們在access(x)後再做splay(x)操作,使得x變爲根方便後續的處理。
<2> find_root(x)
對於find_root(x),只要先access(x),再順着ch[x][0]向左遞歸,遞歸的終點便是root.
<3> make_root(x)
對於make_root(x),只要先access(x),再對x所在整棵splay做reverse,並在翻轉整條鏈之後,把fa[root]賦值給fa[x],使得該splay還具備向其上的splay做轉移的條件。
該操作只會影響(x, root)這條路徑的深度,並且把首端點的延展傳遞。形象而言,就像是一隻蜈蚣,我們抓住其"脊錐",使得(x, root)轉了個頭
<4> 對於link(x, y)操作,我們先make_root(x),把其到實際樹根的整條鏈都取出來,再直接使得fa[x] = y就好了。
<7> 對於cut(x, y)操作,我們先make_root(x),再做access(y)操作,使得提取出了鏈(y, x),把fa[ch[y][0]]和ch[y][0]設爲0,即使得y與y的實際父節點刪了邊
<8> 對於modify(x, y, val)操作,我們先make_root(x),再做access(y)操作,使得提取出了鏈(y, x),再add(y, val)即可
<9> 對於query(x, y)操作,我們先make_root(x),再做access(y)操作,使得取出了鏈(y, x),再return 返回關於y的值即可

【要點】
<1> 既然是splay,因爲涉及到pushup操作,於是我們必須使得根節點處恰好維護着完全子樹的值,操作的點位也要在根處
	同樣的道理,只要是設計從根向下遍歷,就必須做pushdown操作,LCT中的pushdown寫法稍特殊,先找到根再一口氣pushdown,可以減少pushdown的次數。
<2> LCT有時候維護的是有根樹,此時就不需要執行(也不能執行)make_root操作,但是通過access,就能得到該有根樹的祖先。操作在該祖先點上展開即可。

*/

int n;
pair<int, int>edge[N];
vector<int>a[N];
struct LCT
{
	int ID;
	int ch[N][2], fa[N];
	int v[N];
	int mx[N];
	bool rv[N];
	int ad[N];
	void clear(int x)
	{
		ch[x][0] = ch[x][1] = fa[x] = 0;
		rv[x] = ad[x] = 0;
	}
	int newnode(int val)
	{
		int x = ++ID; clear(x);
		mx[x] = v[x] = val;
		return x;
	}
	inline int D(int x)
	{
		return ch[fa[x]][1] == x;
	}
	bool isroot(int x)
	{
		return ch[fa[x]][0] != x && ch[fa[x]][1] != x;
	}
	void reverse(int x)
	{
		if (x == 0)return;
		swap(ch[x][0], ch[x][1]);
		rv[x] ^= 1;
	}
	void add(int x, int val)
	{
		if (x == 0)return;
		v[x] += val;
		mx[x] += val;
		ad[x] += val;
	}
	void pushup(int x)
	{
		mx[x] = v[x];
		gmax(mx[x], max(mx[ch[x][0]], mx[ch[x][1]]));
	}
	void pushdown(int x)
	{
		if (rv[x])
		{
			reverse(ch[x][0]);
			reverse(ch[x][1]);
			rv[x] = 0;
		}
		if (ad[x])
		{
			add(ch[x][0], ad[x]);
			add(ch[x][1], ad[x]);
			ad[x] = 0;
		}
	}
	void rootdown(int x)
	{
		if (!isroot(x))rootdown(fa[x]);
		pushdown(x);
	}
	void setc(int x, int y, int d)
	{
		ch[x][d] = y;
		if (y)fa[y] = x;
		if (x)pushup(x);
	}
	//旋轉操作:旋轉使得節點x與其父節點交換位置
	void rotate(int x)
	{
		int f = fa[x];
		int ff = fa[f];
		bool d = D(x);
		bool dd = D(f);
		setc(f, ch[x][!d], d);		
		setc(x, f, !d);				
		if (ch[ff][dd] == f)setc(ff, x, dd); else fa[x] = ff;		
	}
	//旋轉操作:把x旋轉到其所在splay的根節點
	void splay(int x)
	{
		rootdown(x);
		while (!isroot(x))
		{
			//if (!isroot(fa[x]))pushdown(fa[fa[x]]); pushdown(fa[x]); pushdown(x); // WHY WA
			if (!isroot(fa[x]))rotate(D(x) == D(fa[x]) ? fa[x] : x);
			rotate(x);
		}
	}
	//LCT核心操作:在當前的樹形態下,取出x到root路徑(preferred path)上的所有點,形成auxiliary tree
	//我們只需要對當前爬升到的點now做splay(now)操作並丟掉其右子樹(子孫),過程中自動把祖先的fa[root]轉移爲了fa[now],繼續上跳即可。
	void access(int x)
	{
		for (int son = 0, now = x; now; son = now, now = fa[now])
		{
			splay(now);
			setc(now, son, 1);
		}
		splay(x);
	}
	//LCT基本操作:先找到x所在的auxiliary tree(即preferred path),並查找返回該條路徑最小的節點(即根)
	int find_root(int x)
	{
		access(x);
		while (ch[x][0])pushdown(x), x = ch[x][0];
		return x;
	}
	//LCT基本操作:先找到x所在的auxiliary tree(即preferred path),並把x旋轉爲這條路徑的根
	void make_root(int x)
	{
		access(x);
		reverse(x);
	}
	//LCT基本操作,先使得x爲真實樹的根,再提取出y到x這條路徑所表示的splay
	void getlink(int x, int y)
	{
		make_root(x);
		access(y);
	}
	//LCT重要操作:在x與y不在同一棵樹的條件下,把x變爲子樹的根,並把x鏈接爲y的子節點
	void link(int x, int y)
	{
		if (x == y || find_root(x) == find_root(y)) { puts("-1"); return; }
		make_root(x); 
		fa[x] = y;
	}
	//LCT重要操作:在x與y在同一棵樹的條件下,把x變爲子樹的根,並把y與y祖先之間的邊徹底斷開
	void cut(int x, int y)
	{
		if (x == y || find_root(x) != find_root(y)) { puts("-1"); return; }
		getlink(x, y);
		ch[y][0] = 0; fa[ch[y][0]] = 0;
	}
	//LCT常用操作:在x與y在同一棵樹的條件下,把鏈(x, y)上每個點的權值都+=val
	void modify(int x, int y, int val)
	{
		if (find_root(x) != find_root(y)) { puts("-1"); return; }
		getlink(x, y);
		add(y, val);
	}
	//LCT常用操作:在x與y在同一棵樹的條件下,詢問鏈(x, y)上的最大點權
	int query(int x, int y)
	{
		if (find_root(x) != find_root(y))return - 1;
		getlink(x, y);
		return mx[y];
	}
	//LCT DEBUG
	void alldown(int x)
	{
		pushdown(x);
		if (ch[x][0])alldown(ch[x][0]);
		if (ch[x][1])alldown(ch[x][1]);
	}
	void solve()
	{
		mx[ID = 0] = -1e9;
		for (int i = 1; i < n; ++i)scanf("%d%d", &edge[i].first, &edge[i].second);
		for (int i = 1; i <= n; ++i) { int val; scanf("%d", &val); newnode(val); }
		for (int i = 1; i < n; ++i)link(edge[i].first, edge[i].second);
		int q, op, x, y, val;
		scanf("%d", &q);
		for (int i = 1; i <= q; ++i)
		{
			scanf("%d", &op);
			if (op == 1)
			{
				scanf("%d%d", &x, &y);
				link(x, y);
			}
			else if (op == 2)
			{
				scanf("%d%d", &x, &y);
				cut(x, y);
			}
			else if (op == 3)
			{
				scanf("%d%d%d", &val, &x, &y);
				modify(x, y, val);
			}
			else
			{
				scanf("%d%d", &x, &y);
				printf("%d\n", query(x, y));
			}
		}
		puts("");
	}
}lct;
int main()
{
	while(~scanf("%d", &n))
	{
		lct.solve();
	}
	return 0;
}
/*
論文 —— 《SPOJ375 QTREE 解法的一些研究》
概念學習:
<1> 訪問
	稱一個點被訪問過,如果剛剛執行了對這個點的access操作
<2> preferred child
	如果節點x在子樹中,最後被訪問的節點在子樹y中,且y是x的子節點,那麼我們稱y是x的preferred child
	如果最後被訪問的節點就是x本身,那麼它沒有preferred child,每個點到它preferred child之間的邊稱作preferred edge
<3> preferred path
	整棵樹被劃分成了若干條preferred path,我們對於每條preferred path,用每個點的深度作爲關鍵字,用一棵平衡樹來維護。
	所以在splay中,每個點左子樹中的點,深度都比其小,意爲其祖先,都在preferred path中這個點的上方
				  每個點右子樹中的點,深度都比其大,意爲其子孫,都在preferred path中這個點的下方
<4> Auxiliary Tree
	我們使用splay維護preferred path,把樹T分解成若干條preferred path。
	我們只需要知道這些路徑之間的連接關係,就可以表示出這棵樹T
<5> Link-Cut Tree
	將要維護的森林中的每棵樹T表示爲若干個Auxiliary Tree,通過Splay中的深度關係,將這些Auxiliary Tree連接起來 的數據結構

HDU4010
[題意]
有一棵n(3e5)個點的樹
每個點有一個權值,權值在任何時候都不會超過int範圍
有Q(3e5)個操作,操作包括4種類型
1 x y:Link操作
	要求x與y屬於兩棵不同的樹,此時連接x與y,從而使得兩棵樹合併爲一棵
2 x y:Cut操作
	要求x與y屬於一棵相同的樹,先使得x變爲該樹的根,再切斷y與fa[y]之間的邊,使得一棵樹分裂爲兩棵
3 val x y:修改操作
	使得x與y之間路徑上所有點權都加val
4 x y:詢問操作
	輸出x與y路徑上點權的最大值
[分析]
LCT的模板題。與之類似的——
SPOJ OTOCI:涉及到單點修改和鏈上求和,對x做單點修改的時候只要splay(x)即可,並不需要access(x)
HDU5333:涉及到維護鏈上次大值,雖然代碼量大了很多,但是本質都相同

BZOJ2002
[題意]
有1 ~ n共計n(2e5)個點。
對於每個點i,有一個彈跳距離v[i](正整數),如果i + v[i] <= n,則我們在達到 i 點之後會被彈到 i + v[i]點
否則在到達 i 點之後就會被彈飛。有m(1e5)個操作——
1 x:問你從x出發彈多少次會被彈飛
2 x val:改變v[x]爲val
[分析]
通常而言,LCT的樹邊是雙向邊,這使得在過程中,我們只要確定好了鏈接關係,任意一點爲樹根其實都不太影響
但是,有些題中LCT的樹邊是單向邊,此時就不能再使用LCT的make_root()函數了,參見此題——
對於這道題,我們可以把每次彈跳的起點和終點之間連邊。形成有向樹(也是DAG)。於是——
<1> 對於詢問操作,就是問你每個點到其所在樹根之間的邊數。
<2> 對於修改操作,就是涉及到cut與link兩種操作。
需要注意的是,這道題與傳統的LCT不同,邊是有向邊。
但是我們可以把其當做無向邊來看,心裏上明確跳躍過程是從深度較高的點向深度較低的點進行的就好。
可是在LCT中,我們經常會做make_root(x)這樣之類的操作,這實際中是不允許的。
本題中需要實現的函數是cut()和link(),其中——
cut(x, y)的功能是把x與y之間的邊刪掉,此時我們需要access(x),取出x所在的整條splay(即從x到root的路徑)
然後使得x與其祖先的關聯性完全斷開,即執行ch[x][0] = fa[ch[x][0]] = 0操作
link(x, y)的功能是把x與y之間連邊,因爲每個點的出度都最多爲1.
所以此時顯然x應當是其所在splay的祖先,我們只需要splay(x),fa[x] = y即可(access(x)實際也一樣)。

HDU5967 2016年中國大學生程序設計競賽(合肥)小R與手機
[題意]
n(2e5)個點,每個點x最多一個出度指向v[x]。
告訴你初始的鏈接關係,如果v[x] == 0表示無出度
m(2e5)個操作——
1 x y:使得x指向y
2 x:詢問如果從x出發,是否能走到一個終止節點,有則輸出,無則輸出-1
[分析]
問題還是先分析好再做得好。
這道題的動態鏈接關係顯然可以通過LCT實現。
然而,這個鏈接關係可能會形成環,而LCT顯然是不支持環結構的。怎麼辦?
<1>無環,無環的話,從每個點沿着有向邊出發,最終找到的節點,這個節點指向0即可
		注意,明確對應關係的話,問題會更好做,要知道——這個點就是根節點!
<2>有環,有環的話,其實我們需要使得一個節點的指向關係存儲卻不生效。
		想想看,這個節點,其實也只能是根節點。
得到之前的結論,問題便變得清晰一些了——
1. 我們只要生效所有鏈接關係,比如此時要鏈接(x, y),x是還沒有生效鏈接關係的,但是發現find_root(y) == x,那便發現了環。
   於是我們在物理結構上,x依然是access()後能得到的根(之一),但同時保留了v[x],有v[x]不爲0。即根的v[]不爲0,也就對應着是存在了環
2. 我們過程中還要繼續生效鏈關係,比如此時要鏈接(x, y)。但是——
	對於x,其之前可能也存在着鏈接的目標z
	如果x是根,(x,z)的鏈接關係可能未生效,我們直接相對於對x做嶄新的鏈接即可
	如果(x,z)的鏈接關係已經生效,首先我們一定要斷開(x,z)的鏈接關係,然後對於z所在的tree的root,如果其root和v[root]之間斷邊了,則修改v[root]
	然後的鏈接操作與之前無異,是傻瓜式的。
於是link這麼寫——
void link(int x, int y)
{
	int w = find_root(x);//先找到根節點
	if (v[x] && x != w)cut(x, v[x]);//有邊且不是根節點,就刪邊
	v[x] = y;//標記
	if (y && x != find_root(y))splay(x), fa[x] = v[x];//根據是否有環決定是否加邊,splay(x)而不是access(x)是因爲x顯然是根,其實都一樣
	if (v[w] && x != w && w != find_root(v[w]))splay(w), fa[w] = v[w];//根據是否有環決定是否恢復邊,splay(w)是因爲w顯然是根

	//注意,這裏我們是有判斷x != find_root(y)和 w != find_root(v[w]),左側之所以不用find_root(x)和find_root(w),
	//是因爲左側的點必然是根,而如果x == w,則顯然x因爲指向了y,x就不再是根,即導致最後一句話出錯,於是加了條件x != w
	//其實可以不加這些判定條件,而把x換成find_root(x),把w換成find_root(w)來解決問題。
}
cut這麼寫——(其實這裏的cut不需要參數y)
void cut(int x, int y)
{
	//if (x == y || find_root(x) != find_root(y)) { puts("-1"); return; }
	access(x);
	ch[x][0] = fa[ch[x][0]] = 0;//rgt -> lft
}

HDU5333 LCT動態樹 離線詢問+樹狀數組(本題與HDU4677題意和做法完全相同,只是數據範圍卡住了分塊做法)
[題意]
給你一個n(1e5)點和m(2e5)邊的無向圖。
同時存在q(1e5)個詢問,對於每個詢問,有區間[L, R]
我們只連接兩個端點都在[L, R]中的所有邊,則該圖會形成多少個連通塊。
[分析]
顯然,[1, L - 1] 與 [R + 1, n]中的點都沒有任何邊連接。
於是,答案其實是:(L - 1 + n - R) + [L, R]區間內起到實質效應的邊數。
首先這道題詢問衆多,我們考慮離線化所有詢問。
比如,我們按照右界從小到大的順序,對所有邊做排序,就可以只加入右界不超過R的所有邊。
然而,這樣子形成的圖並不一定是樹,可能形成了圖,這使得我們無法動態維護splay。
於是,我們在加邊的過程中也要動態維護其森林結構。
具體的做法是使用貪心,對於每次新加的邊(不考慮自環和重邊),如果加入該邊會形成環,那麼,
對於加邊(x, y)之間的鏈,和該邊共成的環,我們刪掉環上的任意一條邊,整個圖的連通性都是一樣的。
這裏基於後效性最優的貪心原則,我們只要刪掉該環上左端點最小的邊,該邊的用途是最小的。
於是,我們把邊抽象爲點,原始點的邊權爲極大,邊點的權值爲該邊所對應的較小節點的編號,於是LCT的查詢是樹鏈上的最小節點編號。
因爲右邊界爲R的詢問也有很多,對應很多不同的L,我們用樹狀數組維護此時有從L ~ n的邊加了多少條。
每多一條邊便少一個連通塊,由此更新答案。
void solve()
{
	v[ID = 0] = inf;
	for (int i = 1; i <= n; ++i) newnode(inf), bit.v[i] = 0;
	for (int i = 1; i <= m; ++i) 
	{ 
		scanf("%d%d", &edge[i].l, &edge[i].r); 
		if (edge[i].r < edge[i].l)swap(edge[i].l, edge[i].r);
	}
	for (int i = 1; i <= Q; ++i) 
	{ 
		scanf("%d%d", &q[i].l, &q[i].r); 
		q[i].id = i; 
	}
	sort(edge + 1, edge + m + 1);
	sort(q + 1, q + Q + 1);
	for (int i = 1, p = 1; i <= Q; ++i)
	{
		while (p <= m && edge[p].r <= q[i].r)
		{
			int x = edge[p].l;
			int y = edge[p++].r;
			int o = newnode(x);
			if (x == y || x == edge[p - 2].l && y == edge[p - 2].r)continue;
			if (find_root(x) == find_root(y))
			{
				getlink(x, y);
				int u = mp[y];
				if (v[u] >= x)continue;
				cut(u, edge[u - n].l);
				cut(u, edge[u - n].r);
				bit.modify(v[u], -1);
			}
			link(x, o);
			link(y, o);
			bit.modify(x, 1);
		}
		ans[q[i].id] = n - bit.check(q[i].l);
	}
	for (int i = 1; i <= Q; ++i)printf("%d\n", ans[i]);
}


2014-2015 ACM-ICPC(CERC 14) J LCT動態樹 + 可持久化線段樹 Pork barrel
[題意]
n(1000)個點
m(100000)條邊
q(1e6)個詢問
對於每個詢問[L,R],問你,如果使用的邊的邊權在[L,R]範圍內,那麼——
我們能夠得到的最小邊權的極大生成森林的邊權之和是多少,強制在線。
[分析]
如果可以離線化的話,我們直接把邊按照權值從大到小做排序,也把詢問按照其左界從大到小做排序.
然後枚舉詢問,詢問的左界遞減,我們加的邊數也越多。
對於一條邊,如果其加入成環了,我們會刪掉其所在環上邊權最大的一條邊。
然後對於查詢[L,R],如果有能夠起到同樣功效的大邊,會被刪掉,如果有沒有能夠起到相同功效的大邊,我們也保留了適當的小邊。
也就是說,在樹狀數組中動態維護邊權,check(r)就是答案。
然而,這道題需要使得詢問在線,於是,如果我們還想要使用離線的方法的話,就要記錄下所有可能詢問的答案。
該做法是使用可持久化線段樹(或者可持久化樹狀數組),對於每一個左界我們都維護一棵線段樹,最後查詢就好啦——
void solve()
{
	scanf("%d%d", &n, &m);
	v[ID = 0] = -inf;
	for (int i = 1; i <= n; ++i) newnode(-inf);
	topval = 0; for (int i = 1; i <= m; ++i)
	{
		scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].v);
		rk[++topval] = edge[i].v;
	}
	sort(rk + 1, rk + topval + 1);
	topval = unique(rk + 1, rk + topval + 1) - rk - 1;
	sort(edge + 1, edge + m + 1);
	pst.sz = 0; pst.rt[topval + 1] = 0;
	for(int l = topval, p = 1; l >= 1; --l)
	{
		pst.rt[l] = pst.rt[l + 1];
		while (p <= m && edge[p].v == rk[l])
		{
			int x = edge[p].x;
			int y = edge[p].y;
			int o = newnode(edge[p++].v);
			if (find_root(x) == find_root(y))
			{
				getlink(x, y);
				int u = mp[y];
				if (v[u] <= v[o])continue;
				cut(u, edge[u - n].x);
				cut(u, edge[u - n].y);
				pst.addp(pst.rt[l], 1, topval, lower_bound(rk + 1, rk + topval + 1, v[u]) - rk, -v[u]);
			}
			pst.addp(pst.rt[l], 1, topval, l, v[o]);
			link(x, o);
			link(y, o);
		}
	}
	scanf("%d", &Q);
	int ans = 0;
	for (int i = 1; i <= Q; ++i)
	{
		int l, r; scanf("%d%d", &l, &r);
		l = lower_bound(rk + 1, rk + topval + 1, l - ans) - rk;
		r = upper_bound(rk + 1, rk + topval + 1, r - ans) - rk - 1;
		if (l > r)ans = 0;
		else ans = pst.check(pst.rt[l], 1, topval, l, r);
		printf("%d\n", ans);
	}
}


*/


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