河南省队选拔 HAOI2015 解题报告

 

    其实省选在四天前就已经结束了,但由于题目难度略大我到今天上午才补完所有题目……(捂脸逃)考场上很幸运,打完了所有我会写的部分分,最后Round1的110分 + Round2的70分,勉强算是没有被联赛day2的文件夹坑太多。。。

    目前网上比较容易找到的题解似乎只有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$。

分析.

     从点和点的距离入手不太好分析……我在考场上想了一两个小时最后还是弃疗了,连暴力都没心情写,交了一份靠运气的贪心骗分。

     其实呢……由于树上每一条边都是桥,我们可以从每一条边两端的黑白点数入手,也就是考虑每条边对答案造成的贡献。定义函数f(v, i)表示在v子树中,共将i个点染色后,能得到的“子树中所有点的父边的对答案的贡献之和”的最大值。这样每棵子树就成了一个可分割的子结构,就可以做树形背包了。

     根据官方题解中给出的那种神奇的证明,这样做的时间复杂度是$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( 1,  0,  0);
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个操作,分为三种:

操作1 1 x a:把某个节点x的点权增加a。

操作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( 1,  0);
 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

 

转载于:https://www.cnblogs.com/Asm-Definer/p/4466729.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值