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

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

     今年浙江省選的出題人是算法競賽界傳說級人物陳立傑,看樣子他的出題風格頗有特色……ABC三題難度是嚴格遞減的,感受若是在作第一題的時候被卡住的話恐怕連想死的心都有了……算法

     那麼咱們先從最難的一題開始……= =
數據結構

BZOJ 3924 A.幻想鄉戰略遊戲

      給定一棵N個結點的有正的邊權、初始點權爲0的無根樹,進行M次操做,每次將一個點u的權值增長e($0 \leq |e| \leq 1000$),保證任意時刻點權非負。你的任務是在每次操做後找到一個帶權重心u,使得全部點到重心的距離與點權的乘積之和最小(即最小化$\sum_{v} dist(u, v) × val_v,並輸出這個最小的值。
ide

      N, M均不超過${10}^5$.保證每一個點的度數均不超過20.
函數

分析.

      首先咱們假設每次操做事後咱們能夠快速地在線查詢以任意一個點爲關鍵點獲得的權值和,那麼在這種狀況下如何求出最小權值?
ui

      爲了表達方便,咱們不妨設當前以點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是存在極小值的單峯函數。
this

      那麼咱們設點c爲咱們要求的一個帶權重心。考慮樹上任意一點u和它到c之間的路徑,因爲u的S函數取最小值,又由路徑上S函數值的單峯性,咱們能夠證實在從u到c的路徑上S值是單調遞增的,而相鄰兩點S值相同當且僅當這兩點的S值均爲$S_u$,即最小值。最後這點結論能夠由「零點區間連續」天然地得出。
spa

      有了這條性質,查詢最小權值就好辦了。咱們能夠在樹上任取一點u將樹有根化,判斷它的各鄰接點的S值是否小於$S_u$。若存在一點v使得$S_v < S_u$,那麼根據上面的結論,咱們知道答案必定在v所在的子樹中,遞歸查詢便可。若不存在這樣的點v,那麼答案必定是$S_u$。
code

      聽起來很爽對不對?然而,若是咱們每次在樹上「任取一點」,最壞狀況下遞歸的深度能夠達到$O(N)$級別,時間複雜度退化得很嚴重。怎麼辦呢?咱們能夠在樹上找到一點u,使得以u爲根最大的子樹的規模最小化(通常稱u爲這棵樹的重心)。那麼這樣每棵子樹的規模都不會超過原樹規模的1/2,那麼不難證實此時遞歸查詢的深度就成了$O(\log N)$。
blog

      再來考慮開頭咱們假設的咱們已經會了的操做——在線查詢任意一個$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 }
多串後綴自動機

 

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

相關文章
相關標籤/搜索