NOIP模拟33

T1:

  考虑状压dp,发现传统转移需要记录当前猎人状态

数据范围显然不可行

  考虑转化思路,发现问题所求期望可以进行转化,

利用期望线性性质对期望进行分解,具体表现为:

转化题意:1号猎人死亡轮数期望等价于其余猎人在1号前

死亡期望 + 1,由于其余猎人在1号前死亡贡献为1,故可

直接转化为概率,由于概率期望问题均匀性,故i号猎人比

1号猎人先死概率为W[i] / (W[i] + W[1]),累加即可

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I int
 4 const I N = 1e5 + 7;
 5 const I mod = 998244353;
 6 I n,res,base;
 7 inline I qpow (I a,I b) { I res (1);
 8     for ( ; b ;a = 1ll * a * a % mod, b >>= 1) 
 9       if (b & 1) res = 1ll * res * a % mod; return res;
10 }
11 signed main () { 
12     scanf ("%d%d",&n,&base);
13     for (I i(2),tmp;i <= n; ++ i) scanf ("%d",&tmp), 
14       res = (res + 1ll * tmp * qpow ((base + tmp) % mod,mod - 2) % mod) % mod;
15     printf ("%lld\n", 1ll * ++ res);
16 }
View Code

期望求解:定义式:概率乘贡献,线性分解

T2:

  考虑记录子节点状态转移到父节点,利用bitset维护,

发现空间复杂度过高,考虑使用内存池或分块进行维护,

发现事实上每个节点求解过程中都需要其所有子节点信息

故无法用上述方法进行空间维护

  考虑转化思路,发现问题本质在于子节点信息的继承

考虑使用合并类思想,实际采用线段树合并或set启发式合

并,于是发现设L为前缀0数,R为后缀0数,MID为中缀0数

则问题实际上是求max (MID,R + L) 线段树维护即可

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I int
 4 #define V void
 5 const I N = 1e5 + 7;
 6 I n,m,q,u,v,p,tot,head[N],to[N],nxt[N],root[N],out[N];
 7 vector <I> s[N];
 8 inline V found (I x,I y) {
 9     to[++tot] = y, nxt[tot] = head[x], head[x] = tot;
10 }
11 struct SGT {
12     #define mid (l + r >> 1)
13     I sgt,pre[N*80],nxt[N*80],middle[N*80],lc[N*80],rc[N*80];
14     inline V update (I x,I l,I r) {
15         if (!lc[x] && !rc[x]) {
16             pre[x] = nxt[x] = middle[x] = r - l + 1;
17         }  else
18         if ( lc[x] &&  rc[x]) {
19             pre[x] = pre[lc[x]], nxt[x] = nxt[rc[x]];
20             middle[x] = max (middle[lc[x]],middle[rc[x]]);
21             middle[x] = max (middle[x],pre[rc[x]] + nxt[lc[x]]);
22         }  else 
23         if ( lc[x] && !rc[x]) {
24             pre[x] = pre[lc[x]], nxt[x] = nxt[lc[x]] + r - mid;
25             middle[x] = max (middle[lc[x]],nxt[x]);
26         }  else 
27         if ( rc[x] && !lc[x]) {
28             pre[x] = pre[rc[x]] + mid - l + 1, nxt[x] = nxt[rc[x]];
29             middle[x] = max (middle[rc[x]],pre[x]);
30         }
31     }
32     V insert (I &x,I l,I r,I pos) {
33         if (!x) x = ++ sgt; if (l == r) return ;
34         pos <= mid ? insert (lc[x],l,mid,pos) : insert (rc[x],mid + 1,r,pos);
35         update (x,l,r);
36     }
37     I merge (I x,I y,I l,I r) {
38         if (!x || !y) return x + y; if (l == r) return x;
39         lc[x] = merge (lc[x],lc[y],l,mid);
40         rc[x] = merge (rc[x],rc[y],mid + 1,r);
41         update (x,l,r);
42         return x;
43     }
44 }SGT;
45 V dfs (I x) {
46     for (I i(head[x]),y(to[i]); i ;i = nxt[i],y = to[i])
47       dfs (y), root[x] = SGT.merge (root[x],root[y],1,m);
48     for (auto y : s[x]) SGT.insert (root[x],1,m,y);
49     if (!root[x]) return (V) (out[x] = -1);
50     out[x] = max (SGT.middle[root[x]],SGT.pre[root[x]] + SGT.nxt[root[x]]);
51 }
52 signed main () {
53     cin >> n >> m >> q;
54     for (I i(1);i <  n; ++ i) cin >> u >> v, found (u,v);
55     while (q -- ) cin >> u >> p, s[u].push_back (p); dfs (1);
56     for (I i(1);i <= n; ++ i) cout << out[i] << endl;
57 }
View Code

信息继承的合并思路,问题不仅要微观思考细节,还要宏观

把控其本质。动态开点线段树与线段树合并的思想

T3:

  数据范围提示状压,考虑问题本质状态为有且仅有一条

链连接1-n,于是考虑记录点集状态,考虑新加入点的影响

  first:加入点位于当前链末端,second:加入点位于某

点集,于是需要记录链末元素,分别进行转移即可

  预处理点集信息降低复杂度

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define I int
 4 const I N = 15;
 5 I n,m,u,v,w,dis[N + 1][N + 1],dp[1 << N][N + 1],sum1[1 << N],sum2[1 << N][N + 1];
 6 signed main () {
 7     cin >> n >> m;
 8     while (m -- ) cin >> u >> v >> w, dis[u][v] = dis[v][u] = w;
 9     for (I i(1);i < 1 << n; ++ i) for (I j(1);j <= n; ++ j)
10      for (I k(j + 1);k <= n; ++ k) if (i & 1 << j - 1 && i & 1 << k - 1)
11       sum1[i] += dis[j][k];
12     for (I i(1);i < 1 << n; ++ i) for (I j(1);j <= n; ++ j) if (!(i & 1 << j - 1))
13      for (I k(1);k <= n; ++ k) if (i & 1 << k - 1 && dis[j][k])
14       sum2[i][j] += dis[j][k];   
15     memset (dp,-1,sizeof dp); dp[1][1] = 0;
16     for (I i(1);i < 1 << n; ++ i)
17      for (I j(1);j <= n; ++ j) if (dp[i][j] != -1) {
18       for (I k(1);k <= n; ++ k) if (!(i & 1 << k - 1) && dis[j][k])
19        dp[i | 1 << k - 1][k] = max (dp[i | 1 << k - 1][k],dp[i][j] + dis[j][k]);
20       I state (i ^ (1 << n) - 1);
21       for (I k(state); k ; -- k &= state)
22        dp[i | k][j] = max (dp[i | k][j],dp[i][j] + sum1[k] + sum2[k][j]);
23      }       
24     printf ("%d\n",sum1[(1 << n) - 1] - dp[(1 << n) - 1][n]);
25 }
View Code

dp过程的方向性使得转移过程中省去冗余判断,另一种解释

为为了保证dp转移的方向性,在转移过程中不能出现后向转

移实际降低了问题复杂度。考虑dp设计原理,预处理降低复

杂度但要注意分析预处理的状态的需求量,实际分析。

拓展:子集枚举

 1 设全集为 N = (1 << n) - 1;
 2 tps1:全集枚举
 3     正序:for (int i = 1;i <= N; ++ i)
 4     倒序:for (int i = N; i ; -- i)
 5 设目标集为 N = s;
 6 tps2:倒序枚举
 7     for (int i = N; i ;i = i - 1 & N)
 8     原理:状态合并
 9     事实上存在冗余枚举,为保证枚举N的子集
10     在枚举过程中通过 & N保证状态合法性
11 tps3:正序枚举状态数为k子集
12     int M = (1 << m) - 1;
13     int tmp1, tmp2;
14     for (int i = M;i <= N;i = (i & ~tmp2) / tmp1 >> 1 | tmp2)
15       tmp1 = lowbit(i), tmp2 = i + tmp1;
16     原理:模拟进位(手模即可),详见如下:
17 first : tmp2 = i + tmp1    --> 将最后一位左移(注意存在进位情况)
18 second: i & ~tmp2         --> 左移的变化位置(预处理进位的情况)
19 third : second / tmp1 >> 1 --> 将变化位置移动到末尾(保证状态数)
20 forth : third | tmp2       --> 生成正序下状态数为k子集
View Code

 

posted @ 2021-08-08 21:20  HZOI_LYM  阅读(33)  评论(0编辑  收藏  举报