NOIP 模拟十九

T1:

  很显然,暴力思路很好想,考虑如何优化,能够发现,对于当前操作的

下三角,元素关于矩阵均匀变化,那么考虑并不直接完成这种变化,类似懒

惰标记的思想,我们可以考虑先保留这种影响,在使用的这个点时,在把这

个点所具有的影响传递下去。

  O(qn)的差分很容易想到,直接压行换为序列问题考虑即可,但是复杂度

仍然不够,于是进一步发现,懒惰标记同样关于直线均匀变化,那干脆只记录

关键标记,以此传递标记,也就是利用标记对矩阵元素差分,复杂度降到O(qn)

,在对懒惰标记进行差分,复杂度降到O(q)

代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I int
 4 #define LL long long
 5 const I MAXN = 1e3 + 3;
 6 I n,q,r,c,l,s;
 7 LL res,flag,A[MAXN << 1][MAXN << 1][2];
 8 signed main () {
 9     scanf ("%d %d ",& n,& q);
10     while (q -- ) {
11         scanf ("%d %d %d %d ",& r,& c,& l,& s);
12         A[r][c][1] += s, A[r][c + 1][0] -= s;
13         A[r + l][c][1] -= s, A[r + l][c + l + 1][0] += s;
14     }
15     for (I i(1);i <= n; ++ i) {
16         flag = 0; 
17         for (I j(1);j <= n; ++ j) {
18             flag += A[i][j][1] + A[i][j][0];
19             res ^= flag;
20             A[i + 1][j][1] += A[i][j][1];
21             A[i + 1][j + 1][0] += A[i][j][0];
22         }
23     }
24     printf ("%lld\n",res);
25 }
View Code

T2:

  概率期望,观察复杂度比较迷惑,状压太大,对于其他算法(高斯消元)

又太小,在考虑问题性质,最优策略问题,对于此种问题,常规解决方法:

一是枚举所有决策暴力取最优,二是逆推先计算答案再考虑决策

  本问题很显然具有后效性,即当前决策会对下一次决策造成影响,之前

概率期望专题有一道Red is good,当时最优策略解决方案为,当贡献为负数时

主动舍弃当次贡献,其性质与本题类似

  这启发我们对于最优策略问题,通常基本思路为递推,选择有益贡献。抛弃

负面贡献,对于当先状态,通过遍历其子状态,选择最优结果进行转移,也就是

通常说的倒推(由子状态推出当前状态)

  于是本题直接dfs进行状压即可,复杂度过不去,考虑优化,(这种迷惑数据

很显然是优化),再次分析问题性质发现每次决策都会造成序列的变化,这就导致

可能存在一些不同的操作产生了相同的序列,于是本题就可以记忆化(本问题的性质

是这道题可以记忆化的关键)考虑手写散列表或直接用map(注意由于map时空问题

对于不同的数据需要不同处理)记忆化每种状态的结果

  关于时间复杂度证明,此种记忆化的时间复杂度瓶颈在于操作与结果的关系

考虑构造一种序列使得在不同操作下,不同的结果数尽可能多,于是WBWBWBWB

WBWBWBWBWBWBWBWB应运而生,分析不同子序列数(很显然是个数学板子)

发现对于每个W/B都有选与不选两种状态,类比组合数的递推构造一种方案数DP

发现答案对于长度为n的序列,其子序列数为为fib数列第n+1项,于是求和即可

  关于手写散列表,存在两种写法,适用与不同情况,考虑应用hash是想降低复杂度

是的key与val一一对应,通常情况下由于空间限制无法开的太大,但是本问题存在一个

性质为不同的序列长度间不存在存储关系,于是可以对于不同的长度建立不同的散列表。

代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I int
 4 #define D double
 5 #define V void
 6 const I MAXN = 32;
 7 I n,k,seq;
 8 char init[MAXN];
 9 namespace HASH {
10     #define repre (x % mod)
11     const I mod = 1226959;
12     const I SIZE = mod + 3;
13     struct MAP {
14         I tot,key[SIZE],head[SIZE],nxt[SIZE];
15         D value[SIZE];
16         inline V insert (I x,D val) {
17             key[++tot] = x, value[tot] = val;
18             nxt[tot] = head[repre], head[repre] = tot;
19         }
20         inline D find (I x) {
21             for (I i(head[repre]); i ;i = nxt[i])
22               if (key[i] == x)
23                 return value[i];
24             return -1;
25         }
26     }h[MAXN];
27 }
28 inline I del (I x,I pos) {
29     I tmp = x & ((1 << pos) - 1);
30     x >>= pos + 1,x <<= pos;
31     return x | tmp;
32 }
33 D dfs (I len,I seq) {
34     if (len > k) return 0.0;
35     D val = HASH :: h[len].find (seq);
36     if (val >= 0.0) return val;
37     D a,b,E(0.0);
38     for (I i(1);i <= n - len + 1; ++ i) {
39         I oppo = n - len - i + 2;
40         a = dfs (len + 1,del (seq,i - 1)) + ((seq >> i - 1) & 1);           
41         b = dfs (len + 1,del (seq,oppo - 1)) + ((seq >> oppo - 1) & 1);
42         E += (a > b ? a : b) / (n - len + 1);
43     }
44     HASH :: h[len].insert (seq,E);
45     return E;
46 }
47 signed main () {
48     cin >> n >> k;
49     scanf ("%s",init + 1);
50     for (I i(1);i <= n; ++ i)
51       seq |= (init[i] == 'W') << i - 1;
52     printf ("%.10lf",dfs (1,seq));       
53 }
View Code

 T3:

  首先分析题目性质,树上最优性问题,当然可以考虑树上问题转序列问题,然而本

题不同与星空,对于区间操作由仅仅的0,1单向变换转化为0,1定向变换,于是序列难

以维护

  通常解决最优性问题采取递推(DP)手段,发现问题在以任意节点为根的子树都相同

(即最优子结构性质)[注意,考虑将递推视作有向无环图的遍历,那么节点极为状态,也

是转移的基本单位(状压dp就是以一种状态为转移的基本单位),而图中的连边极为状态

的转移方式,图中每一层极为转移的一个阶段。那么递推的关键接在于连边(状态转移方

程)的求解,依次由递推边界借助转移方程进行逐层转移],于是问题转化为在已知子树信

息情况下如何拓展。

  分析发现,对于一条已知路径,其最小操作次数为路径上(关于本路径)度数为奇数的点

的一半,因为若度数为奇数,那么其必然是某条子路径的端点,而度数为偶数则无法判断其

关于该路径的位置,于是发现一条路径有两个端点,因此求出度数为奇数的点(端点)除2

即可。于是问题就由抽象的操作数转化为奇度数点的个数,只要在转移过程中记录信息,在

边界处更新信息即可,关于最短路径长度,其由最小操作数关键决定(即首先保证最小操作

数),故二元转移,先转移最小操作,在以此更新最小路径长度

代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I int
 4 #define V void
 5 const I MAXN = 1e5 + 3;
 6 const I INT = 2e5 + 3;
 7 I n,a,b,c,d,tot,head[MAXN];
 8 pair<I,I>dp[MAXN][2];
 9 struct NODE {
10     I to,nxt,ori,lat;
11 }node[MAXN << 1];
12 inline V found (I a,I b,I c,I d) {
13     node[++tot].to = b,node[tot].nxt = head[a],node[tot].ori = c,node[tot].lat = d,head[a] = tot;
14     node[++tot].to = a,node[tot].nxt = head[b],node[tot].ori = c,node[tot].lat = d,head[b] = tot;
15 }
16 inline pair<I,I> fuse (pair<I,I> a,pair<I,I> b) {
17     return make_pair(a.first + b.first,a.second + b.second);
18 }
19 V dfs (I x,I edge) {
20     pair <I,I> up ( make_pair(INT,INT) ), down ( make_pair(0,0) );
21     for (I i(head[x]),y(node[i].to); i ;i = node[i].nxt,y = node[i].to)
22       if (i != (edge ^ 1)) {
23         dfs (y,i);
24         pair<I,I> sup1 (up), sup2 (down);
25         up = min (fuse (sup1,dp[y][0]),fuse (sup2,dp[y][1]));
26         down = min (fuse (sup1,dp[y][1]),fuse (sup2,dp[y][0]));
27       }
28     if (node[edge].ori == (node[edge].lat ^ 1)) 
29       dp[x][1] = min(fuse (up,make_pair(0,1)),fuse (down,make_pair(1,1))), dp[x][0] = make_pair(INT,INT);
30     else if (node[edge].ori == node[edge].lat)
31       dp[x][1] = make_pair(INT,INT), dp[x][0] = min(fuse (up,make_pair(1,0)),down);
32     else dp[x][1] = min(fuse (up,make_pair(0,1)),fuse (down,make_pair(1,1))), dp[x][0] = min(fuse (up,make_pair(1,0)),down);
33 }       
34 signed main () {
35     cin >> n; tot = 1;
36     for (I i(1);i < n; ++ i) {
37         cin >> a >> b >> c >> d;
38         found (a,b,c,d);
39     }
40     dfs (1,0);
41     cout << dp[1][0].first / 2 << ' ' << dp[1][0].second;
42 }
View Code

 

posted @ 2021-07-19 08:39  HZOI_LYM  阅读(52)  评论(0)    收藏  举报