最近莫名其妙地喜歡上了用這種格式寫各省省選的全套題解= =
今年浙江省選的出題人是算法競賽界傳說級人物陳立傑,看樣子他的出題風格很有特點……ABC三題難度是嚴格遞減的,感覺如果在做第一題的時候被卡住的話恐怕連想死的心都有了……
那么我們先從最難的一題開始……= =
BZOJ 3924 A.幻想鄉戰略游戲
給定一棵N個結點的有正的邊權、初始點權為0的無根樹,進行M次操作,每次將一個點u的權值增加e($0 \leq |e| \leq 1000$),保證任意時刻點權非負。你的任務是在每次操作后找到一個帶權重心u,使得所有點到重心的距離與點權的乘積之和最小(即最小化$\sum_{v} dist(u, v) × val_v,並輸出這個最小的值。
N, M均不超過${10}^5$.保證每個點的度數均不超過20.
分析.
首先我們假設每次操作過后我們可以快速地在線查詢以任意一個點為關鍵點得到的權值和,那么在這種情況下如何求出最小權值?
為了表達方便,我們不妨設當前以點u為關鍵點求得的權值和為$S_u$,那么我們不難發現這樣一個性質:在樹上任意兩點a, b之間的路徑上,$S_u$構成了一個存在極小值的單峰函數。證明也很簡單:考慮路徑上任意一條邊e,設e兩端點分別為s, t,兩端連接的點集分別為S, T,邊權為e.v。則關鍵點從s走到t的過程中權值和的變化量:$$\Delta = S_t - S_s = (\sum_{u \in S} val_u - \sum_{v \in T} val_v) * e.v.$$ 而在轉移的過程中,點t和它的不在鏈上的后代結點都將從T集合轉移到S集合,即 $(\sum_{u \in S} val_u - \sum_{v \in T} val_v)$ 是單調遞增的,又由題意得知邊權都是正整數,因此函數 $\Delta = S_t - S_s = (\sum_{t \in S} val_u- \sum_{v \in T} val_v) * e.v.$ 的零點區間一定唯一(由於$\Delta$是離散函數,這里“零點區間”指的是左右兩側函數值正負性相反的區間),且左負右正。由於$\Delta$表示的是S函數的增量,那么$\Delta$的零點區間唯一且左負右正就證明了S是存在極小值的單峰函數。
那么我們設點c為我們要求的一個帶權重心。考慮樹上任意一點u和它到c之間的路徑,由於u的S函數取最小值,又由路徑上S函數值的單峰性,我們可以證明在從u到c的路徑上S值是單調遞增的,而相鄰兩點S值相同當且僅當這兩點的S值均為$S_u$,即最小值。最后這點結論可以由“零點區間連續”自然地得出。
有了這條性質,查詢最小權值就好辦了。我們可以在樹上任取一點u將樹有根化,判斷它的各鄰接點的S值是否小於$S_u$。若存在一點v使得$S_v < S_u$,那么根據上面的結論,我們知道答案一定在v所在的子樹中,遞歸查詢即可。若不存在這樣的點v,那么答案一定是$S_u$。
聽起來很爽對不對?然而,如果我們每次在樹上“任取一點”,最壞情況下遞歸的深度可以達到$O(N)$級別,時間復雜度退化得很嚴重。怎么辦呢?我們可以在樹上找到一點u,使得以u為根最大的子樹的規模最小化(一般稱u為這棵樹的重心)。那么這樣每棵子樹的規模都不會超過原樹規模的1/2,那么不難證明此時遞歸查詢的深度就成了$O(\log N)$。
再來考慮開頭我們假設的我們已經會了的操作——在線查詢任意一個$S_u$。
考慮我們剛才建立的重心分治結構。對點v進行修改時,我們可以花費$O(\log N)$的時間更新v所在的每一層分治結構的重心維護的答案(即在分治u維護的答案中增加$dist(u, v) * \Delta val_v$),並記錄每層分治結構中的結點對上一層分治維護的答案的貢獻。在對點v查詢時,先將答案設為v分治中維護的答案,然后向上移動累加答案:在從分治current向它的上一層分治parent移動時,在parent維護的答案中減去current對它的貢獻得到$\delta S$,將得到的結果臨時當做點v的后代累加進答案。即$Ans = lastAns + \delta S + (Sum_{parent} - Sum_{current}) * dist(parent, v) $,其中$Sum_t$表示t維護的分治結構中所有點權的平凡加和。這樣,我們就會做這道題了。
如果我們用倍增LCA法求dist,時間復雜度為$O((N+M) \log^3 N) $,可能有些卡常數。考慮到操作不會改變原樹的結構,我們可以在$O(N \log N)$的時間內預處理后通過ST表維護DFS序列來求LCA,總時間復雜度$O((N+M) \log^2 N)$.
代碼.

2 /* *********************By Asm.Def-Wu Jiaxin**************************** */
3 /* ********************************************************************* */
4 #include <cstdio>
5 #include <cstring>
6 #include <cstdlib>
7 #include <ctime>
8 #include <cctype>
9 #include <algorithm>
10 using namespace std;
11 #define SetFile(x) ( freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout) );
12 #define UseFREAD
13 #ifdef UseFREAD
14 #define getc() *(file_ptr++)
15 #define FreadLenth 5000000
16 char CHARPOOL[FreadLenth], *file_ptr = CHARPOOL;
17 #else
18 #define getc() getchar()
19 #endif
20 #ifdef DEBUG
21 #include <sys/timeb.h>
22 timeb SysTp;
23 #endif
24 template< class T>inline void getd(T &x){
25 char ch = getc(); bool neg = false;
26 while(!isdigit(ch) && ch != ' - ')ch = getc();
27 if(ch == ' - ')ch = getc(), neg = true;
28 x = ch - ' 0 ';
29 while(isdigit(ch = getc()))x = x * 10 - ' 0 ' + ch;
30 if(neg)x = -x;
31 }
32 /* ********************************************************************* */
33 const int maxn = 100005;
34 typedef long long LL;
35
36 int N, Q, lg2[maxn << 1], dfs_iter;
37
38 struct Node{
39 Node *adj[ 22], *p, *Brid;
40 int w[ 22], adj_d, dep, dis, id; // 用於距離查詢
41 int size; // 臨時變量
42 LL Val, Sum, Cont, Ans; // 樹分治
43 int AnsTag;
44 bool tag;
45 inline void Link(Node *t, int v){
46 w[adj_d] = v;
47 adj[adj_d++] = t;
48 }
49 void dfs(); void dfs2();
50 Node *dp( int, int &);
51 LL Query();
52 inline void GetAns();
53 }T[maxn], *ST[ 18][maxn<< 1], *Root;
54
55 void Node::dfs(){
56 (*ST)[id = dfs_iter++] = this;
57 int i;Node *to;
58 for(i = 0;i < adj_d;++i) if((to = adj[i]) != p){
59 to->p = this, to->dep = dep + 1, to->dis = dis + w[i];
60 to->dfs();
61 (*ST)[dfs_iter++] = this;
62 }
63 }
64
65 inline Node * Cmp(Node *a, Node *b){ return a->dep > b->dep ? b : a;}
66
67 inline void Build_ST(){
68 int i = 2, j = 1, t = 4;
69 while(i <= dfs_iter){
70 if(i == t)++j, t <<= 1;
71 lg2[i++] = j;
72 }
73 for(i = 1, t = 2;t <= dfs_iter;++i, t <<= 1) for(j = 0;j + t <= dfs_iter;++j)
74 ST[i][j] = Cmp(ST[i- 1][j], ST[i- 1][j+(t >> 1)]);
75 }
76
77 inline int dist(Node *a, Node *b){
78 int u = a->id, v = b->id; if(v < u)swap(u, v);
79 int len = v - u + 1, lg = lg2[len];
80 return a->dis + b->dis - (Cmp(ST[lg][u], ST[lg][v+ 1-( 1 << lg)])->dis << 1);
81 }
82
83 void Node::dfs2(){
84 size = 1;
85 int i;Node *to;
86 for(i = 0;i < adj_d;++i) if(!(to = adj[i])->tag && to != p){
87 to->p = this;
88 to->dfs2();
89 size += to->size;
90 }
91 }
92
93 Node * Node::dp( int psize, int &Maxsize){
94 Node *ans = this, *to, *t;Maxsize = psize;
95 int mx, i;
96 for(i = 0;i < adj_d;++i) if((!(to = adj[i])->tag) && to != p)
97 Maxsize = max(Maxsize, to->size);
98 psize += size;
99 for(i = 0;i < adj_d;++i) if((!(to = adj[i])->tag) && to != p){
100 t = to->dp(psize - to->size, mx);
101 if(mx < Maxsize)ans = t, Maxsize = mx;
102 }
103 return ans;
104 }
105
106 Node *Build_DC(Node *t){
107 int s;t->p = NULL;
108 t->dfs2();
109 Node *root = t->dp( 0, s), *to;
110 root->tag = true;root->Brid = t;
111 for( int i = 0;i < root->adj_d;++i){
112 if((to = root->adj[i])->tag)swap(root->adj[i--], root->adj[--root->adj_d]);
113 else{
114 root->adj[i] = Build_DC(to);
115 root->adj[i]->p = root;
116 }
117 }
118 return root;
119 }
120
121 inline void Modify(Node *v, int d){
122 v->Sum += d;
123 Node *t = v;
124 LL cont;
125 while(t != Root){
126 LL &s_cont = t->Cont;
127 t = t->p;
128 t->Sum += d;
129 cont = (LL)dist(v, t) * d;
130 t->Val += cont;s_cont += cont;
131 }
132 }
133
134 inline void Node::GetAns(){
135 if(AnsTag == Q) return; // 避免一次操作后重復查詢
136 Ans = Val;
137 Node *t = p;
138 LL sum_d = Sum, cont = Cont, diff, dsum;
139 while(t){
140 diff = t->Val - cont;dsum = t->Sum - sum_d;
141 if(dsum)Ans += diff + dsum * dist(t, this);
142 sum_d = t->Sum, cont = t->Cont;
143 t = t->p;
144 }
145 AnsTag = Q;
146 }
147
148 LL Node::Query(){
149 GetAns();
150 int i;Node *to;
151 for(i = 0;i < adj_d;++i){
152 (to = adj[i])->Brid->GetAns();
153 if(to->Brid->Ans < Ans) return to->Query();
154 }
155 return Ans;
156 }
157
158 inline void init(){
159 int i, a, b, c;
160 for(i = 1;i < N;++i){
161 getd(a), getd(b), getd(c);
162 T[a].Link(T + b, c);
163 T[b].Link(T + a, c);
164 }
165 T[ 1].dfs();
166 Build_ST();
167 (Root = Build_DC(T + 1))->p = 0x0;
168 }
169
170 int main(){
171
172 #ifdef DEBUG
173 freopen( " test.txt ", " r ", stdin);ftime(&SysTp);
174 size_t Begin_sec = SysTp.time, Begin_mill = SysTp.millitm;
175 #elif !defined ONLINE_JUDGE
176 SetFile(zjoi15_tree);
177 #endif
178 #ifdef UseFREAD
179 fread(file_ptr, 1, FreadLenth, stdin);
180 #endif
181
182 int u, e;
183 getd(N), getd(Q);
184
185 init();
186
187 while(Q){
188 getd(u), getd(e);
189 Modify(T + u, e);
190 printf( " %lld\n ", Root->Query());
191 --Q;
192 }
193
194 #ifdef DEBUG
195 ftime(&SysTp);
196 printf( " \n%.3lf sec \n ", (SysTp.time - Begin_sec) + (SysTp.millitm - Begin_mill) / 1000.0);
197 #endif
198 return 0;
199 }
BZOJ 3925 B.地震后的幻想鄉
給定一個n個點m條邊的無向圖,每條邊的權值在0~1之間隨機選取,求這張圖的最小瓶頸生成樹的瓶頸的期望值。
點數不超過10, 保證無重邊且無自環。
分析.
分析.
由於題目中的最小瓶頸生成樹的瓶頸大小是連續變量,我們無法通過簡單地枚舉答案和概率來求解。不妨這樣考慮:設$F_S(x)$表示圖中所有小於x的邊能使點集S連通的概率。那么點集S的最小瓶頸為x的概率就是$$P_{(ans=x)} = \lim_{\Delta x \to 0} F_S(x+\Delta x) - F_S(x)$$也就是$$P_{(ans=x)} = F_S '(x) \mathrm{d} x$$
由期望的定義,我們有:$$E = \int_0^1 x * F_S ' (x) \mathrm{d} x$$是個乘積復合函數,我們對它做分部積分:$$E = \left. \left( x \int F_S '(x) \mathrm{d} x - \int (x' \int F_S '(x) \mathrm{d} x) \right) \right|_0^1 $$得到:$$E = \left. \left( x F_S(x) - \int F_S(x) \mathrm{d} x \right) \right|_0^1$$那么$$E = 1 - \left. \int F_S(x) \mathrm{d} x \right|_0^1$$
那么我們只需要用dp求出整個圖的F_V函數,再積分一下就可以得到答案了。
點集連通的概率似乎沒法直接求?那么我們來考慮相反的情況,點集S不連通的概率。我們可以任選一點$v_0$,枚舉它所在的連通塊S',並計算出S'與$S - S'$的割邊數量cnt,那么$$F_S = 1 - \sum_{S' \ni v_0 \land S' \subsetneqq S} (1 - x) ^ {cnt} $$這樣就可以求出答案了。
代碼.

2 #include <cstring>
3 #include <cstdlib>
4 #include <ctime>
5 #include <cctype>
6 #include <algorithm>
7 #include <cmath>
8 using namespace std;
9 // #define USEFREAD
10 #ifdef USEFREAD
11 #define InputLen 5000000
12 char *ptr=( char *) malloc(InputLen);
13 #define getc() (*(ptr++))
14 #else
15 #define getc() (getchar())
16 #endif
17 #define SetFile(x) (freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout))
18 template< class T>inline void getd(T &x){
19 int ch = getc(); bool neg = false;
20 while(!isdigit(ch) && ch != ' - ')ch = getc();
21 if(ch == ' - ')ch = getc(), neg = true;
22 x = ch - ' 0 ';
23 while(isdigit(ch = getc()))
24 x = x * 10 - ' 0 ' + ch;
25 if(neg)x = -x;
26 }
27 /* ********************************************************************* */
28 #ifdef DEBUG
29 #define __float128 double // 因為蒟蒻的編譯器太老了不支持__float128...
30 #endif
31 typedef long long LL;
32 #include <vector>
33 typedef vector<LL> Poly;
34
35 inline void operator += (Poly &a, const Poly &b){ // 要求a != b
36 Poly ans(max(a.size(), b.size()), 0);
37 for(unsigned i = 0;i < ans.size();++i){
38 if(i < a.size())ans[i] += a[i];
39 if(i < b.size())ans[i] += b[i];
40 }
41 a = ans;
42 }
43
44 inline Poly operator * ( const Poly &a, const Poly &b){
45 Poly ans(a.size() + b.size() - 1, 0);
46 unsigned i, j;LL k;
47 for(i = 0;i < a.size();++i){
48 k = a[i];
49 for(j = 0;j < b.size();++j)ans[i + j] += k * b[j];
50 }
51 return ans;
52 }
53
54 inline Poly operator - ( const Poly &a, const Poly &b){
55 Poly Ans(max(a.size(), b.size()), 0);
56 unsigned i, end = min(Ans.size(), b.size());
57 for(i = 0;i < a.size();++i)Ans[i] = a[i];
58 for(i = 0;i < end;++i)Ans[i] -= b[i];
59 return Ans;
60 }
61
62 Poly op( 2), True( 1, 1), F[ 1025], Pow[ 26];
63
64 int N, M, adj[ 11], USet;
65 inline void init(){
66 int i, u, v;
67 getd(N), getd(M);
68 USet = ( 1 << N) - 1;
69 while(M--){
70 getd(u), getd(v);
71 --u, --v;
72 adj[u] |= ( 1 << v);
73 adj[v] |= ( 1 << u);
74 }
75 op[ 0] = 1, op[ 1] = - 1;
76 Pow[ 0] = True;
77 for(i = 1;i <= 25;++i)Pow[i] = Pow[i- 1] * op;
78 }
79
80 inline void Calc(){
81 __float128 Ans = 0;
82 unsigned i, end = F[USet].size();
83 for(i = 0;i < end;++i)Ans += (__float128)F[USet][i] / (i + 1);
84 printf( " %.6lf\n ", 1.0 - ( double)Ans);
85 }
86
87 #include <queue>
88 bool inc[ 1025] = { 0, 1}; // 是否是包含1的連通塊
89
90 #ifdef DEBUG
91 void Print(Poly x){
92 for( int i = x.size()- 1;i >= 0;--i)printf( " %d ", x[i]);
93 putchar( ' \n ');
94 }
95 #endif
96
97 inline void work(){
98 int t, i, j, b, e, cnt, it;
99 queue< int> Q;Q.push( 1);
100 while(!Q.empty()){
101 t = Q.front();Q.pop();
102 for(j = 0, b = 1;j < N;++j, b <<= 1) if(t & b){
103 e = adj[j];
104 while(e){
105 e ^= (i = e & -e);
106 if(t & i || inc[t ^ i]) continue;
107 inc[t ^ i] = true;
108 Q.push(t ^ i);
109 }
110 }
111 }
112 F[ 1] = True;
113 for(i = 2;i <= USet;++i) if(inc[i]){
114 for(j = (i - 1) & i;j;(--j) &= i) if(inc[j]){
115 cnt = 0;
116 for(t = 0, b = 1;t < N;++t, b <<= 1) if(j & b){
117 e = adj[t];
118 while(e){
119 e ^= (it = e & -e);
120 if((i ^ j) & it)++cnt;
121 }
122 }
123 F[i] += F[j] * Pow[cnt];
124 }
125 F[i] = True - F[i];
126 }
127 /* #ifdef DEBUG
128 for(i = 1;i <= USet;++i)if(inc[i]){
129 printf("%d: ", i);
130 Print(F[i]);
131 }
132 #endif */
133 Calc();
134 }
135
136 int main(){
137 #ifdef DEBUG
138 freopen( " test.txt ", " r ", stdin);
139 #else
140 SetFile(zjoi15_mst);
141 #endif
142 #ifdef USEFREAD
143 fread(ptr, 1,InputLen,stdin);
144 #endif
145
146 init();
147
148 work();
149
150 #ifdef DEBUG
151 printf( " \n%.3lf sec \n ", ( double)clock() / CLOCKS_PER_SEC);
152 #endif
153 return 0;
154 }
C.諸神眷顧的幻想鄉
給定一棵N個結點的樹,每個結點有一個顏色,求樹上的所有路徑經過的不同顏色序列的數量。
N不超過100000,保證樹上的葉子結點數量不超過20,顏色值不超過10.
分析.
一道比較良心的題……保證了葉子結點不超過20個,我們就可以枚舉所有的葉子,分別遍歷一遍整棵樹,對得到的所有序列建立多串后綴數據結構,查詢不同的子串數即可。用廣義SAM實現起來比較容易。
代碼.

2 /* *********************By Asm.Def-Wu Jiaxin**************************** */
3 /* ********************************************************************* */
4 #include <cstdio>
5 #include <cstring>
6 #include <cstdlib>
7 #include <cctype>
8 #include <algorithm>
9 using namespace std;
10 #define SetFile(x) ( freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout) );
11 #define UseFREAD
12 #ifdef UseFREAD
13 #define getc() *(file_ptr++)
14 #define FreadLenth 5000000
15 char CHARPOOL[FreadLenth], *file_ptr = CHARPOOL;
16 #else
17 #define getc() getchar()
18 #endif
19 #ifdef DEBUG
20 #include <ctime>
21 #endif
22 template< class T>inline void getd(T &x){
23 char ch = getc(); bool neg = false;
24 while(!isdigit(ch) && ch != ' - ')ch = getc();
25 if(ch == ' - ')ch = getc(), neg = true;
26 x = ch - ' 0 ';
27 while(isdigit(ch = getc()))x = x * 10 - ' 0 ' + ch;
28 if(neg)x = -x;
29 }
30 /* ********************************************************************* */
31 const int maxn = 100003, maxs = 1000000;
32 typedef long long LL;
33 int N, c, *adj[maxn], d[maxn], col[maxn], leaf[ 22], lcnt;
34
35 struct SAM{
36 SAM *son[ 10], *link;
37 int len;
38 LL cnt;
39 }Node[maxs<< 1], *iter = Node + 1;
40
41 inline SAM * insert(SAM *last, int ch){
42 SAM *cur = iter++, *it, *s, *clone;
43 cur->len = last->len + 1;
44 it = last;
45 while(it && !it->son[ch])it->son[ch] = cur, it = it->link;
46 if(!it)cur->link = Node;
47 else{
48 if(it->len + 1 == (s = it->son[ch])->len)cur->link = s;
49 else{
50 clone = iter++;*clone = *s;clone->len = it->len + 1;
51 cur->link = s->link = clone;
52 do{
53 it->son[ch] = clone;
54 it = it->link;
55 } while(it && it->son[ch] == s);
56 }
57 }
58 return cur;
59 }
60
61 void Build(SAM *last, int cur, int p){
62 SAM *tmp = insert(last, col[cur]);
63 int son;
64 for( int i = 0;i < d[cur];++i) if((son = adj[cur][i]) != p)
65 Build(tmp, adj[cur][i], cur);
66 }
67
68 inline void init(){
69 getd(N), getd(c);
70 size_t intsize = sizeof( int);
71 int i, a, b, u[maxn], v[maxn], it[maxn] = { 0};
72 for(i = 1;i <= N;++i)getd(col[i]);
73 for(i = 1;i < N;++i){
74 getd(u[i]), getd(v[i]);
75 ++d[u[i]], ++d[v[i]];
76 }
77 for(i = 1;i <= N;++i){
78 adj[i] = ( int*) malloc(intsize * (d[i] + 1));
79 if(d[i] == 1)leaf[lcnt++] = i;
80 }
81 for(i = 1;i < N;++i){
82 a = u[i], b = v[i];
83 adj[a][it[a]++] = b;
84 adj[b][it[b]++] = a;
85 }
86 for(i = 0;i < lcnt;++i)Build(Node, leaf[i], 0);
87 }
88
89 int main(){
90
91 #ifdef DEBUG
92 freopen( " test.txt ", " r ", stdin);
93 #elif !defined ONLINE_JUDGE
94 SetFile(zjoi15_substring);
95 #endif
96
97 #ifdef UseFREAD
98 fread(file_ptr, 1, FreadLenth, stdin);
99 #endif
100
101 init();
102 LL Ans = 0;
103 for(SAM *it = Node + 1;it != iter;++it)Ans += it->len - it->link->len;
104 printf( " %lld\n ", Ans);
105
106 #ifdef DEBUG
107 printf( " \n%.3lf sec \n ", ( double)clock() / CLOCKS_PER_SEC);
108 #endif
109 return 0;
110 }