浙江省隊選拔 ZJOI2015 (Round 1) 解題報告


     最近莫名其妙地喜歡上了用這種格式寫各省省選的全套題解= =

     今年浙江省選的出題人是算法競賽界傳說級人物陳立傑,看樣子他的出題風格很有特點……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)$.

代碼.

  1  /* ********************************************************************* */
  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( intint &);
 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 }
重心分治+ST算法求LCA

 

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} $$這樣就可以求出答案了。

代碼.

  1 #include <cstdio>
  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() -  10);
 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( 11), 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] = { 01}; // 是否是包含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 }
概率多項式+狀壓dp

 

C.諸神眷顧的幻想鄉

 給定一棵N個結點的樹,每個結點有一個顏色,求樹上的所有路徑經過的不同顏色序列的數量。

  N不超過100000,保證樹上的葉子結點數量不超過20,顏色值不超過10.

分析.

     一道比較良心的題……保證了葉子結點不超過20個,我們就可以枚舉所有的葉子,分別遍歷一遍整棵樹,對得到的所有序列建立多串后綴數據結構,查詢不同的子串數即可。用廣義SAM實現起來比較容易。

代碼. 

  1  /* ********************************************************************* */
  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 }
多串后綴自動機

 

另附相關資料: 出題人的題解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM