河南省队选拔 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)$.
代码.
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
分析.
其实我们这样来看……对于一次操作(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的深度的一次函数,那么每次进行操作时只要分别修改两项的系数就可以了。
代码.
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 }
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})$,总算是不用玄学完美地解决了= =
代码.
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 }
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 }
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)$$
那么我们的最终答案就可以表示为
由于F函数不会超过1,可直接利用幂级数求和得出答案:$$ \sum_{S' \subseteq S} (-1)^{|S| - |S'|} * ( \frac{1}{F_1(S') - 1} ) $$
而计算一个函数的子集和函数可以用标程中给出的代码用$O(n2^n)$的时间复杂度求出。这样,这道题就完美解决啦!
代码.
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)$ 。值得注意的是……这题卡常数,矩阵乘法一定要尽量减少取模的次数。
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 }