河南省隊選拔 HAOI2015 解題報告

 

    其實省選在四天前就已經結束了,但因爲題目難度略大我到今天上午才補完全部題目……(捂臉逃)考場上很幸運,打完了全部我會寫的部分分,最後Round1的110分 + Round2的70分,勉強算是沒有被聯賽day2的文件夾坑太多。。。
git

    目前網上比較容易找到的題解彷佛只有ydc教主的這份:http://ydc.blog.uoj.ac/blog/336,不過對於我這種水平的人來講這份題解還不是很好理解,那麼我再來稍微補充一點更基礎的內容吧。。
算法

A.T1

    有一棵點數爲N的樹,樹邊有邊權。給你一個在0~N以內的正整數K,你要在這棵樹中選擇K個點,將其染成黑色,並將其餘的N-K個點染成白色。將全部點染色後,你會得到黑點兩兩之間的距離加上白點兩兩之間距離的和的收益。問收益最大值是多少。 數組

    其中$1 \leq N \leq 2000, 0 \leq K \leq N$。
ide

分析.

     從點和點的距離入手不太好分析……我在考場上想了一兩個小時最後仍是棄療了,連暴力都沒心情寫,交了一份靠運氣的貪心騙分。
函數

     其實呢……因爲樹上每一條邊都是橋,咱們能夠從每一條邊兩端的黑白點數入手,也就是考慮每條邊對答案形成的貢獻。定義函數f(v, i)表示在v子樹中,共將i個點染色後,能獲得的「子樹中全部點的父邊的對答案的貢獻之和」的最大值。這樣每棵子樹就成了一個可分割的子結構,就能夠作樹形揹包了。
spa

     根據官方題解中給出的那種神奇的證實,這樣作的時間複雜度是$O(N^2)$.指針

代碼.

 1  /* ********************************************************************* */
 2  /* *********************By Asm.Def-Wu Jiaxin**************************** */
 3  /* ********************************************************************* */
 4 #include 
 5 #include 
 6 #include 
 7 #include 
 8 #include 
 9 #include 
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 
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 =  2003;
34 typedef  long  long LL;
35 #include 
36  struct Edge{ int to, w;};
37 vector adj[maxn];
38 
39 LL f[maxn][maxn], S[maxn][maxn];
40  int N, K, size[maxn];
41 
42 inline  void UPD(LL &a,  const LL &b){ if(a < b)a = b;}
43 
44  void dfs( int cur,  int p,  int w){
45     vector::iterator it;
46      int i, j, to;
47     size[cur] =  1;
48      for(it = adj[cur].begin();it != adj[cur].end();++it) if((to = (*it).to) != p){
49         dfs(to, cur, (*it).w);
50          for(i = min(size[cur], K);i >=  0;--i) for(j = min(size[to], K - i);j >=  0;--j)
51             UPD(S[cur][i + j], S[cur][i] + f[to][j]);
52         size[cur] += size[to];
53     }
54      for(i = size[cur];i >=  0;--i)
55         f[cur][i] = S[cur][i] + (LL)w * (i * (K - i) + (N - K - size[cur] + i) * (size[cur] - i));
56 }
57 
58 inline  void work(){
59     getd(N), getd(K);
60      int i, a, b, c;
61      for(i =  1;i < N;++i){
62         getd(a), getd(b), getd(c);
63         adj[a].push_back((Edge){b, c});
64         adj[b].push_back((Edge){a, c});
65     }
66     dfs( 100);
67     printf( " %lld\n ", f[ 1][K]);
68 }
69 
70  int main(){
71 
72 #ifdef DEBUG
73     freopen( " test.txt "" r ", stdin);ftime(&SysTp);
74     size_t Begin_sec = SysTp.time, Begin_mill = SysTp.millitm;
75  #elif !defined ONLINE_JUDGE
76     SetFile(haoi2015_t1);
77  #endif
78 
79 #ifdef UseFREAD
80     fread(file_ptr,  1, FreadLenth, stdin);
81  #endif
82 
83     work();
84 
85 #ifdef DEBUG
86     ftime(&SysTp);
87     printf( " \n%.3lf sec \n ", (SysTp.time - Begin_sec) + (SysTp.millitm - Begin_mill) /  1000.0);
88  #endif
89      return  0;
90 }
樹形揹包

 

B.T2

有一棵點數爲N的樹,以點1爲根,且樹點有邊權。而後有M個操做,分爲三種: code

操做1 1 x a:把某個節點x的點權增長a。 blog

操做2 2 x a:把某個節點x爲根的子樹中全部點的點權都增長a。 遊戲

操做3 3 x:詢問某個節點x到根的路徑中全部點的點權和。

 

對於100%的數據,N,M<=100000,且全部輸入數據的絕對值都不會超過10^6

 

分析.

 

 

     看到「子樹修改」,咱們首先想到的應該是用dfs序列將每層子樹映射到一段區間上,這樣用線段樹或Fenwick樹維護一下就能夠輕鬆完成操做1和操做3。那麼如今的難點就是如何實現操做2。(插一句:考場上我已經敲好了dfs序列和線段樹,而後才發現我不知道怎麼修改整棵子樹……)

     其實咱們這樣來看……對於一次操做(2 u x),咱們來考慮此次操做對u的某個後代v的貢獻。不難看出,從u到v路徑上的每一個點的權值都被增長了x,這個操做對v的答案的增量$d_v = (dist(u, v) + 1) * x$ 。然而dist(u, v)這個東西彷佛不太容易做爲區間修改的增量。因爲u是v的祖先,咱們不妨把dist拆一下,那麼$d_v = (dep_v - dep_u + 1) * x$,或者$d_v = dep_v * x - (dep_u - 1) * x$.(其中$dep_i$表示i點到根的距離)這時咱們其實已經把此次操做的貢獻拆成了一個關於點v的深度的一次函數,那麼每次進行操做時只要分別修改兩項的係數就能夠了。

代碼.

  1  /* *************************************************************************** */
  2  /* *****************************Designed By Asm.Def*************************** */
  3  /* *************************************************************************** */
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 #include 
  9 #include 
 10  // #define FREAD
 11 #ifdef FREAD
 12  #define FREADLENTH 5000000
 13  char *fread_ptr = ( char*) malloc(FREADLENTH);
 14  #define getc() (*(fread_ptr++))
 15  #else
 16  #define getc() getchar()
 17  #endif
 18  #define SetFile(x) ( freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout) )
 19  using  namespace std;
 20 template< class T>inline  void getd(T &x){
 21      int ch = getc(); bool neg =  false;
 22      while(!isdigit(ch) && ch !=  ' - ')ch = getc();
 23      if(ch ==  ' - ')neg =  true, ch = getc();
 24     x = ch -  ' 0 ';
 25      while(isdigit(ch = getc()))x = x *  10 -  ' 0 ' + ch;
 26      if(neg)x = -x;
 27 }
 28  /* ***************************************************************************** */
 29  const  int maxn =  100005;
 30 typedef  long  long LL;
 31 #include 
 32 vector< int> adj[maxn];
 33 
 34  int N, val[maxn], Begin[maxn], End[maxn], dep[maxn];
 35 
 36  struct SegT{
 37      int L, R, mid;
 38     LL Sum, tg;
 39     SegT *ls, *rs;
 40     SegT( int l,  int r):L(l), R(r), mid((l+r)>> 1){
 41         Sum = tg =  0;
 42          if(l == mid){
 43             ls = rs = NULL;
 44              return;
 45         }
 46         ls =  new SegT(l, mid);rs =  new SegT(mid, r);
 47     }
 48     inline  void push(){Sum += tg * (R - L); if(ls)ls->tg += tg, rs->tg += tg;tg =  0;}
 49      void Add( int l,  int r, LL d){
 50          if(l == L && r == R){
 51             tg += d;push();
 52              return;
 53         }
 54         push();
 55          if(r <= mid)ls->Add(l, r, d);
 56          else  if(l >= mid)rs->Add(l, r, d);
 57          else ls->Add(l, mid, d), rs->Add(mid, r, d);
 58         Sum = ls->Sum + rs->Sum;
 59     }
 60     LL Query( int i){
 61          if(tg)push();
 62          if(L == mid) return Sum;
 63          if(i < mid) return ls->Query(i);
 64          if(i >= mid) return rs->Query(i);
 65     }
 66 }*R1, *R2;
 67     
 68  void dfs( int cur,  int p){
 69      static  int Iter =  0;
 70     Begin[cur] = Iter++;
 71     vector< int>::iterator it;
 72      for(it = adj[cur].begin();it != adj[cur].end();++it) if(*it != p){
 73         dep[*it] = dep[cur] +  1;
 74         dfs(*it, cur);
 75     }
 76     End[cur] = Iter;
 77 }
 78 
 79 inline  void init(){
 80      int i, a, b;
 81      for(i =  1;i <= N;++i)getd(val[i]);
 82      for(i =  1;i < N;++i){
 83         getd(a), getd(b);
 84         adj[a].push_back(b);
 85         adj[b].push_back(a);
 86     }
 87     dfs( 10);
 88     R1 =  new SegT( 0, N);R2 =  new SegT( 0, N);
 89      for(i =  1;i <= N;++i)R1->Add(Begin[i], End[i], val[i]);
 90 }
 91 
 92 inline  void opt1(){
 93      int x, d;getd(x), getd(d);
 94     R1->Add(Begin[x], End[x], d);
 95 }
 96 
 97 inline  void opt2(){
 98      int x, d;getd(x), getd(d);
 99     R1->Add(Begin[x], End[x], (LL)( 1 - dep[x]) * d);
100     R2->Add(Begin[x], End[x], d);
101 }
102 
103 inline  void query(){
104      int x;getd(x);
105     printf( " %lld\n ", R1->Query(Begin[x]) + R2->Query(Begin[x]) * dep[x]);
106 }
107 
108  int main(){
109 
110 #ifndef DEBUG
111     SetFile(haoi2015_t2);
112  #else
113     freopen( " test.txt "" r ", stdin);
114  #endif
115 #ifdef FREAD
116     fread(fread_ptr,  1, FREADLENTH, stdin);
117  #endif
118      int M, opt;getd(N), getd(M);
119     init();
120      while(M--){
121         getd(opt);
122          if(opt ==  1)opt1();
123          else  if(opt ==  2)opt2();
124          else query();
125     }
126 
127 #ifdef DEBUG
128     printf( " \n%.3lf sec\n ", ( double)clock() / CLOCKS_PER_SEC);
129  #endif
130      return  0;
131 }
dfs序列(區間修改+單點查詢)

C.T3

 

有一個長度爲N的數組,甲乙兩人在上面進行這樣一個遊戲:

首先,數組上有一些格子是白的,有一些是黑的。而後兩人輪流進行操做。每次操做選擇一個白色的格子,假設它的下標爲x。接着,選擇一個大小在1~n/x之間的整數k,而後將下標爲x、2x、...、kx的格子都進行顏色翻轉。不能操做的人輸。

如今甲(先手)有一些詢問。每次他會給你一個數組的初始狀態,你要求出對於這種初始狀態他是否有必勝策略。假設兩人總能作出最優決策。

對於30%的數據,N<=20;

對於50%的數據,N<=1000000;

對於70%的數據,N<=10000000;

對於100%的數據,N<=1000000000,K,W<=100,不會有格子在同一次詢問中屢次出現。 

分析.

     好難啊……TAT考場上果斷30分暴搜……

     出題人kzf告訴咱們,能夠修改一下游戲的定義:每一步能夠選擇黑格子或白格子,最終能將全部格子都翻成黑色的人贏。因爲每次翻轉黑格子的操做都不可能直接帶來勝利,而任何一步選取黑格子進行的有利操做都能由對方經過再翻一次這個格子抵消掉,根據假設「兩我的都是足夠聰明的」,咱們知道遊戲中必定不會有人選擇黑格子。這就保證了這種轉換的正確性。

     所以咱們能夠把每一個黑色的位置都當作偶數個白格子疊加起來獲得的,並將每一個位置當作一個獨立的遊戲來計算SG函數,就能夠套用常規的博弈題思路了。根據遊戲規則,咱們有$$SG(i) = \mathop{mex} \limits_{1 \leq k \leq N / i} \{ \mathop{\oplus } \limits_{2 \leq t \leq k} \{ SG(t*i) \}\}$$(其中mex(S)表示S中沒有出現的最小天然數)按定義遞推就能夠拿到50分了。

     考慮到當一個點i走到N要跳的步數必定時,咱們能夠把這些i的倍數的序列映射到另外一個序列2...N/i,這樣咱們就能夠用數學概括證實每一個點i的SG值只與從它跳到N須要的次數(即 N / i)有關。而後咱們能夠注意到,i從1到N中,N / i只會有O($\sqrt{N})$種取值,因此咱們只需遞推$O(\sqrt{N})$輪就能夠了。然而……在遞推時不管是維護一個指針線性遞推仍是用二分查找結構在線查找都只能拿到70分……

     而後出題人kzf又告訴咱們,存在不少相鄰的按N/i劃分的區間有着相同的SG值(緣由大概是跳動次數增長時多跳的那一次改變的那些SG和不容易剛好等於增長以前的SG值?反正我不會證實……)。因此咱們在預處理時只要判斷是否能和上一塊合併就能夠了。複雜度……嗯……大概是O(玄學 × $N$)。不過實際跑起來仍是挺快的……

 

     Update: 考慮上面說的70分算法,在遞推的過程當中咱們須要儘量快地查詢之前求出的SG值,那麼咱們直接用一個哈希表就能夠解決問題了!(我之前還沒寫過哈希表……太弱啦……)時間複雜度大概是$T(N) = \sum_{i=1}^{\sqrt{N}} (\sqrt{\frac{N}{i}} + \sqrt{i} ) × \frac{N}{P}$,這個求出來大概是$O(\frac{N^{\frac{7}{4}}}{P})$,總算是不用玄學完美地解決了= =

代碼.

 1  /* *************************************************************************** */
 2  /* *****************************Designed By Asm.Def*************************** */
 3  /* *************************************************************************** */
 4 #include 
 5 #include 
 6 #include 
 7 #include 
 8 #include 
 9 #include 
10  #define SetFile(x) (freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout) )
11  // #define FREAD
12  #define FREADLENTH 5000000
13 #ifdef FREAD
14  char *fread_ptr = ( char*) malloc(FREADLENTH);
15  #define getc() (*(fread_ptr++))
16  #else
17  #define getc() getchar()
18  #endif
19  using  namespace std;
20 template< class T>inline  void getd(T &x){
21      int ch = getc(); bool neg =  false;
22      while(!isdigit(ch) && ch !=  ' - ')ch = getc();
23      if(ch ==  ' - ')neg =  true, ch = getc();
24     x = ch -  ' 0 ';
25      while(isdigit(ch = getc()))x = x *  10 -  ' 0 ' + ch;
26      if(neg)x = -x;
27 }
28  /* ***************************************************************************** */
29  const  int maxn =  64000; // SG[len]
30  int N, val[maxn], vcnt, vt[maxn], checklist[maxn];
31  bool check[maxn];
32  int SG[maxn], id[maxn], idcnt;
33  #define ind(x) ( upper_bound(id, id + idcnt, x) - id - 1 )
34 inline  void init(){
35     getd(N);
36      int i, j, t, s, it, vtcnt, c, tmp, lastSG =  0;
37      int ccnt; // 回滾check數組
38      for(i =  1;i * i <= N;++i)val[vcnt++] = i;
39      for(j = N / i;j;--j)val[vcnt++] = N / j;
40      for(i =  0;i < vcnt;++i){
41         t = val[i];s = ccnt = vtcnt =  0;
42          for(j =  1;j * j <= t;++j)vt[vtcnt++] = j;
43          for(j = t / j;j;--j)vt[vtcnt++] = t / j; // vt:能跳到的位置的N/i的全部取值
44          for(it = idcnt- 1, j = vtcnt- 2;j >=  0;--j){
45              while(id[it] > vt[j])--it;
46             tmp = SG[it];
47             c = t / vt[j] - t / (vt[j] +  1); // 出現次數
48             check[checklist[ccnt++] = s ^ tmp] =  true;
49              if(c &  1)s ^= tmp;
50         }
51         j =  1; while(check[j])++j;
52         id[idcnt] = t;
53          if(j != lastSG)lastSG = SG[idcnt++] = j;
54          for(j =  0;j < ccnt;++j)check[checklist[j]] =  false;
55     }
56 }
57 
58 inline  void work(){
59      int K, qcnt, sum, t;
60     getd(K); while(K--){
61         getd(qcnt);
62         sum =  0;
63          while(qcnt--){
64             getd(t);
65             sum ^= SG[ind(N / t)];
66         }
67          if(sum)puts( " Yes ");
68          else puts( " No ");
69     }
70 }
71 
72  int main(){
73 
74 #ifdef DEBUG
75     freopen( " test.txt "" r ", stdin);
76  #else
77     SetFile(haoi2015_t3);
78  #endif
79 #ifdef FREAD
80     fread(fread_ptr,  1, FREADLENTH, stdin);
81  #endif
82     init();
83     work();
84 
85 #ifdef DEBUG
86     printf( " \n%.3lf sec\n ", ( double)clock() / CLOCKS_PER_SEC);
87  #endif
88      return  0;
89 }
SG定理+遞推+分塊+玄學

 

  1  /* *************************************************************************** */
  2  /* *****************************Designed By Asm.Def*************************** */
  3  /* *************************************************************************** */
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 #include 
  9 #include 
 10  #define SetFile(x) (freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout) )
 11  // #define FREAD
 12  #define FREADLENTH 5000000
 13 #ifdef FREAD
 14  char *fread_ptr = ( char*) malloc(FREADLENTH);
 15  #define getc() (*(fread_ptr++))
 16  #else
 17  #define getc() getchar()
 18  #endif
 19  using  namespace std;
 20 template< class T>inline  void getd(T &x){
 21      int ch = getc(); bool neg =  false;
 22      while(!isdigit(ch) && ch !=  ' - ')ch = getc();
 23      if(ch ==  ' - ')neg =  true, ch = getc();
 24     x = ch -  ' 0 ';
 25      while(isdigit(ch = getc()))x = x *  10 -  ' 0 ' + ch;
 26      if(neg)x = -x;
 27 }
 28  /* ***************************************************************************** */
 29  const  int maxn =  64000, P =  3500017;
 30  int N, val[maxn], vcnt, vt[maxn], checklist[maxn];
 31  bool check[maxn];
 32  int SG[maxn];
 33 
 34  int First[P], Next[P];
 35 
 36 inline  int ind( int x){
 37      int t = First[x % P];
 38      while(t && val[t] != x)t = Next[t];
 39      return t;
 40 }
 41 
 42 inline  void push( int x,  int ind){
 43     Next[ind] = First[x];
 44     First[x] = ind;
 45 }
 46 
 47 inline  void init(){
 48     getd(N);
 49      int i, j, t, s, vtcnt, c, tmp;
 50      int ccnt; // 鍥炴粴check鏁扮粍
 51      for(i =  1;i * i <= N;++i)val[++vcnt] = i;
 52      for(j = N / i;j;--j)val[++vcnt] = N / j;
 53      for(i =  1;i <= vcnt;++i){
 54         t = val[i];s = ccnt = vtcnt =  0;
 55          for(j =  1;j * j <= t;++j)vt[vtcnt++] = j;
 56          for(j = t / j;j;--j)vt[vtcnt++] = t / j; // vt:涔榢涔嬪悗鑳藉嚭鐜扮殑闀垮害
 57          for(j = vtcnt- 2;j >=  0;--j){
 58             tmp = SG[ind(vt[j])];
 59             c = t / vt[j] - t / (vt[j] +  1); // 鍑虹幇嬈℃暟
 60             check[checklist[ccnt++] = s ^ tmp] =  true;
 61              if(c &  1)s ^= tmp;
 62         }
 63         j =  1; while(check[j])++j;
 64         SG[i] = j;
 65         push(t % P, i);
 66          for(j =  0;j < ccnt;++j)check[checklist[j]] =  false;
 67     }
 68 }
 69 
 70 inline  void work(){
 71      int K, qcnt, sum, t;
 72     getd(K); while(K--){
 73         getd(qcnt);
 74         sum =  0;
 75          while(qcnt--){
 76             getd(t);
 77             sum ^= SG[ind(N / t)];
 78         }
 79          if(sum)puts( " Yes ");
 80          else puts( " No ");
 81     }
 82 }
 83 
 84  int main(){
 85 
 86 #ifdef DEBUG
 87     freopen( " test.txt "" r ", stdin);
 88  #else
 89     SetFile(haoi2015_t3);
 90  #endif
 91 #ifdef FREAD
 92     fread(fread_ptr,  1, FREADLENTH, stdin);
 93  #endif
 94     init();
 95     work();
 96 
 97 #ifdef DEBUG
 98     printf( " \n%.3lf sec\n ", ( double)clock() / CLOCKS_PER_SEC);
 99  #endif
100      return  0;
101 }
SG定理+遞推+分塊+哈希

 

 D.set

剛開始你有一個數字0,每一秒鐘你會隨機選擇一個[0,2^n-1]的數字,與你手上的數字進行按位或(C和C++的|,Pascal的or)操做。

選擇數字i的機率是p[i]。保證0<=p[i]<=1,∑p[i]=1

問指望多少秒後,你手上的數字變成2^n-1。

對於30%的數據,n<=10

對於60%的數據,n<=15

對於100%的數據,n<=20

分析.

     呂凱風巨神的的集訓隊論文題(雖然論文暫時還沒發)……

     考場上我寫了個錯誤的遞推,不過我機率論學得渣不知道爲何不對,總之我在考場上嘗試了好久都跑不出樣例,最終直接輸出個INF就放那了= =

     換個思路,咱們嘗試用常規的指望的定義來求。設函數$f_k (S) $爲第k輪集合爲S的機率,那麼答案就是

$$\sum_{i=1}^{\infty} i * (f_i (U) - f_{i-1} (U) )$$

     不難寫出函數f的遞推式:

$$f_{i+1}(S) = \sum_a \sum_b [a \cup b = S] f_{i}(a) * P(b)$$

     不過這個式子看上去不太好轉移對吧?咱們能夠給它加個特技,不要管它們的並集是誰了,咱們直接來考慮等式兩邊S集合的全部子集的函數值之和(這步變換能夠經過集合論中的莫比烏斯反演來逆轉),即:

$$\sum_{s' \subseteq S} f_{i+1}(s') = \sum_{a \subseteq S} \sum_{b \subseteq S}  f_i(a) * P(b)$$

或$$\sum_{s' \subseteq S} f_{i+1}(s') = (\sum_{a \subseteq S} f_i(a) ) (\sum_{b \subseteq S} P(b))$$

發現了什麼嗎?某一項在一個集合上的答案的子集和剛好等於前一項上同一集合的子集和與這一集合的初始機率函數(也就是f_1函數)的子集和的乘積。換句話說,若是咱們設$F_i (S) = \sum_{s' \subseteq S} f_i (s')$,那麼咱們的轉移方程就能夠寫成$F_{i+1}(S) = F_i (S) * F_1 (S) $。瞬間變得很是簡單有木有!!!什麼求和都沒有了,咱們馬上就能夠獲得F的通項公式:$F_i (S) = (F_1 (S)) ^ i$.

     那麼如今咱們的$F_i$函數就已知了,咱們來看看怎麼由$F_i$函數推出$f_i$。咱們不妨令$f_i (S) = \sum_{S' \subseteq S} \mu(S', S) F_i(S')$,其中$\mu(S',  S)$是咱們要求的莫比烏斯函數(注意這裏指的是集合論中廣義的莫比烏斯反演)。將F代入,可得

$$f_i(S) = \sum_{S' \subseteq S} \mu(S', S) \sum_{S'' \subseteq S'} f_i(S'')$$ 由容斥原理能夠得出,$\mu(S', S) = (-1)^{|S| - |S'|}$。從而咱們能夠獲得:$$f_i (S) = \sum_{S' \subseteq S} (-1)^{|S| - |S'|} F_i(S')$$.代入$F_i$(S')的通項公式,可得:$$f_i(S) = \sum_{S' \subseteq S} (-1)^{|S| - |S'|} (F_1 (S')) ^ i)$$

 那麼咱們的最終答案就能夠表示爲

$$\sum_{i=1}^{\infty} (\sum_{S' \subseteq S} (-1)^{|S| - |S'|} i * (F_1 (S')) ^ i - (F_1 (S')) ^ {i-1}))$$
或$$ \sum_{S' \subseteq S}  (-1)^{|S| - |S'|} *  ( - \sum_{i=0}^{\infty} F_1 (S')^i ) $$

 

因爲F函數不會超過1,可直接利用冪級數求和得出答案:$$ \sum_{S' \subseteq S}  (-1)^{|S| - |S'|} *  ( \frac{1}{F_1(S') - 1} ) $$

而計算一個函數的子集和函數能夠用標程中給出的代碼用$O(n2^n)$的時間複雜度求出。這樣,這道題就完美解決啦!

代碼.

 1  /* ********************************************************************* */
 2  /* *********************By Asm.Def-Wu Jiaxin**************************** */
 3  /* ********************************************************************* */
 4 #include 
 5 #include 
 6 #include 
 7 #include 
 8 #include 
 9 #include 
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 
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 =  1 <<  20;
34  const  double eps = 1e- 8;
35  double P[maxn];
36  int n, N;
37 inline  double m_abs( double x) { return x <  0 ? -x : x;}
38 inline  void work(){
39     getd(n);N =  1 << n;
40      int i, j, t, tmp;
41 
42      for(i =  0;i < N;++i){
43         scanf( " %lf ", P + i);
44          if(P[i])tmp |= i;
45     }
46      if(tmp+ 1 != N){
47         puts( " INF ");
48          return;
49     }
50 
51      for(t =  1;t < N;t <<=  1) // Modulate
52          for(j =  0;j < N;++j) if(j & t)P[j] += P[j ^ t];
53 
54      for(j =  0;j < N;++j){
55          if(m_abs(P[j] -  1.0) < eps)P[j] =  0;
56          else P[j] =  1.0 / (P[j] -  1.0);
57     }
58 
59      for(t =  1;t < N;t <<=  1) // Demodulate
60          for(j =  0;j < N;++j) if(j & t)P[j] -= P[j ^ t];
61 
62     printf( " %.10lf\n ", P[N -  1]);
63 }
64 
65  int main(){
66 
67 #ifdef DEBUG
68     freopen( " test.txt "" r ", stdin);ftime(&SysTp);
69     size_t Begin_sec = SysTp.time, Begin_mill = SysTp.millitm;
70  #elif !defined ONLINE_JUDGE
71     SetFile(haoi2015_set);
72  #endif
73 
74 #ifdef UseFREAD
75     fread(file_ptr,  1, FreadLenth, stdin);
76  #endif
77 
78     work();
79 
80 #ifdef DEBUG
81     ftime(&SysTp);
82     printf( " \n%.3lf sec \n ", (SysTp.time - Begin_sec) + (SysTp.millitm - Begin_mill) /  1000.0);
83  #endif
84      return  0;
85 }
集合多項式+莫比烏斯反演

 

E.str

 

你有一個長度爲n的數字串。

定義f(S)爲將S拆分紅若干個1~m的數的和的方案數,好比m=2時,f(4)=5,分別爲4=1+1+1+1, 4=1+1+2, 4=1+2+1, 4=2+1+1, 4=2+2

你能夠將這個數字串分割成若干個數字(容許前導0),將它們加起來,求f,並求和。

好比g(123)=f(1+2+3)+f(1+23)+f(12+3)+f(123)。

已知字符串和m後求答案對998244353(7*17*223+1,一個質數)取模後的值。

對於30%的數據,字符串長度不超過5

對於60%的數據,字符串長度不超過18

對於100%的數據,字符串長度不超過500,m<=5

分析.

     不難看出,$f(i) = \sum_{j=1}^m f(i-j)$.很容易把這個遞推式寫成一個m × m的矩陣M。暫時撇開f函數前m項構成的向量$F_0$,咱們考慮對這整個轉移矩陣作dp。設g(i)表示可以將$F_0$轉移到$F_{num_{0, i}}$的轉移矩陣。其中$num_{i, j}$表示字符串的第i+1到j位構成的十進制數。

     根據矩陣乘法對加法的分配律,能夠獲得$g(i) = \sum_{j = 0}^{i-1} g(j) * M ^ {num(j, i)}$,先用十進制矩陣快速冪的思路求出F的10的各冪次的冪,而後dp求出最終的矩陣,最後左乘向量求值便可。總複雜度$O({len}^2 m^3)$ 。值得注意的是……這題卡常數,矩陣乘法必定要儘可能減小取模的次數。

 

  1  /* ********************************************************************* */
  2  /* *********************By Asm.Def-Wu Jiaxin**************************** */
  3  /* ********************************************************************* */
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 #include 
  9 #include 
 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 
 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 =  505, mod =  998244353;
 34 typedef  long  long LL;
 35 
 36  int n, num[maxn], len, Vect[ 5];
 37 
 38  struct Mat{ int A[ 5][ 5];}I, F, dp[maxn], Pow[ 10][maxn], Pow10[ 10];
 39 inline Mat  operator * ( const Mat &a,  const Mat &b){
 40     Mat ans;
 41      int i, j, k;
 42     unsigned  long  long tmps;
 43      for(i =  0;i < n;++i) for(j =  0;j < n;++j){
 44         tmps =  0;
 45          for(k =  0;k < n;++k)tmps += (LL)a.A[i][k] * b.A[k][j];
 46         ans.A[i][j] = tmps % mod;
 47     }
 48 
 49      return ans;
 50 }
 51 
 52 inline  void  operator += (Mat &a,  const Mat &b){
 53      int i, j;
 54      for(i =  0;i < n;++i) for(j =  0;j < n;++j){
 55         a.A[i][j] += b.A[i][j];
 56          if(a.A[i][j] >= mod)a.A[i][j] -= mod;
 57     }
 58 }
 59 
 60 inline Mat power(Mat a,  int t){
 61     Mat ans = I;
 62      while(t){ if(t &  1)ans = ans * a;a = a * a;t >>=  1;}
 63      return ans;
 64 }
 65 
 66 inline  void init(){
 67      int i, j, ch = getc(); while(!isdigit(ch))ch = getc();
 68      while(isdigit(ch)){
 69         num[len++] = ch -  ' 0 ';
 70         ch = getc();
 71     }
 72     getd(n);
 73     *Vect =  1; for(i =  1;i < n;++i) for(j =  0;j < i;++j)Vect[i] += Vect[j];
 74      for(i =  1;i < n;++i)F.A[i][i- 1] =  1;
 75      for(i =  0;i < n;++i)F.A[i][n- 1] =  1;
 76      for(i =  0;i < n;++i)I.A[i][i] =  1;
 77     Pow10[ 0] = I;Pow10[ 1] = F;
 78      for(i =  2;i <  10;++i)Pow10[i] = Pow10[i- 1] * F;
 79     Mat tmp = I;
 80      for(i =  1;i <  10;++i){
 81         tmp = tmp * F;
 82         Pow[i][ 1] = tmp;
 83          for(j =  2;j <= len;++j)Pow[i][j] = power(Pow[i][j- 1],  10);
 84     }
 85 }
 86 
 87 inline  void work(){
 88      int i, j, t;
 89     Mat tmp;
 90     dp[ 0] = I;
 91      for(i =  1;i <= len;++i){
 92         tmp = I;
 93          for(j =  1;j <= i;++j){
 94              if((t = num[i-j]))tmp = tmp * Pow[t][j];
 95             dp[i] += dp[i - j] * tmp;
 96         }
 97     }
 98      int Ans =  0;
 99      for(i =  0;i < n;++i)Ans = ((LL)dp[len].A[i][ 0] * Vect[i] + Ans) % mod;
100     printf( " %d\n ", Ans);
101 }
102 
103  int main(){
104 
105 #ifdef DEBUG
106     freopen( " test.txt "" r ", stdin);
107  #elif !defined ONLINE_JUDGE
108     SetFile(haoi2015_str);
109  #endif
110 #ifdef UseFREAD
111     fread(file_ptr,  1, FreadLenth, stdin);
112  #endif
113     init();
114     work();
115 
116 #ifdef DEBUG
117     printf( " \n%.3lf sec \n ", ( double)clock() / CLOCKS_PER_SEC);
118  #endif
119      return  0;
120 }
矩陣+十進制快速冪+dp
相關文章
相關標籤/搜索