$A$ 正常的签到?
然而我写了多长一大坨屎山代码
暴力字符串匹配,然后将每个密码子能匹配上的位置起止位置作为线段的左右端点,则问题转化为选择尽量多的互不重叠的线段,贪心即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <cstdio> #include <algorithm> #include <cstring> int fa[1000005 ], l[1000005 ], r[1000005 ];char s[10005 ], p[100 ][5 ];bool removed[1000005 ];struct Line { int l, r; inline bool operator < (const Line X) const {return l < X.l;} } li[10005 ]; int find (const int x) {return fa[x] == x ? x : fa[x] = find (fa[x]);}int main () { int n, m (0 ), t (0 ), MaxR (0 ), ans (0 ); scanf ("%s" , s + 1 ); n = strlen (s + 1 ); while (scanf ("%s" , p[m + 1 ] + 1 ) != EOF) ++ m; for (int i (1 ); i <= m; ++ i) for (int j (1 ); j + 2 <= n; ++ j) if (s[j] == p[i][1 ] && s[j + 1 ] == p[i][2 ] && s[j + 2 ] == p[i][3 ]) li[++ t].l = j, li[t].r = j + 2 ; std::sort (li + 1 , li + t + 1 ); for (int i (1 ); i <= t; ++ i) fa[i] = i; for (int i (t); i; -- i) if (li[i - 1 ].r > li[find (i)].r) removed[i - 1 ] = true , fa[i - 1 ] = find (i); m = 0 ; for (int i (1 ); i <= t; ++ i) if (!removed[i]) l[++ m] = li[i].l, r[m] = li[i].r; for (int i (1 ); i <= m; ++ i) if (!removed[i] && MaxR < l[i]) MaxR = r[i], ++ ans; printf ("%d" , ans); return 0 ; }
$B$ 贪心。考虑按照罪犯指控关系建图,则将目前所有入度为 $0$ 的点设定为罪犯,删掉从这个点出发的边,从这个点开始dfs。
因为图有环,最后再扫一遍,从所有入度为 $1$ 的点开始 dfs 一遍。
感性理解一下这个贪心应该是正确的。严格证明不会,老师也没讲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <cstdio> int A[500005 ], In[500005 ], n, ans;bool vis[500005 ];void dfs (const int u, const int mark) { if (vis[u]) return ; vis[u] = true ; ans += mark; if (-- In[A[u]] == 0 || mark) dfs (A[u], mark ^ 1 ); } int main () { scanf ("%d" , &n); for (int i (1 ); i <= n; ++ i) scanf ("%d" , A + i), ++ In[A[i]]; for (int i (1 ); i <= n; ++ i) if (In[i] == 0 ) dfs (i, 1 ); for (int i (1 ); i <= n; ++ i) if (In[i] == 1 ) dfs (i, 0 ); printf ("%d" , ans); return 0 ; }
$C$ 你既可以状压也可以贪心,考试时觉得状压代码也很短,贪心反而不好严格证明(虽然我找不出hack)就写了状压。
状压做的话就是板子题了。$dp_S$ 表示打完 $S$ 集合中的英雄最小所需HP。转移方程不再赘述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <cstdio> #include <algorithm> #include <cstring> inline int min (const int x, const int y) {return x < y ? x : y;}int dp[1 << 20 ], h[20 ], d[20 ], sum[1 << 20 ];int main () { memset (dp, 0x3f , sizeof dp); dp[0 ] = 0 ; int n; scanf ("%d" , &n); for (int i (0 ); i < n; ++ i) scanf ("%d%d" , d + i, h + i); for (int i (1 ); i < 1 << n; ++ i) { for (int j (0 ); j < n; ++ j) if (i & 1 << j) sum[i] += d[j]; } for (int i (1 ); i < 1 << n; ++ i) { for (int j (0 ); j < n; ++ j) if (i & 1 << j) dp[i] = min (dp[i], dp[i ^ 1 << j] + sum[i] * h[j]); } printf ("%d" , dp[(1 << n) - 1 ]); return 0 ; }
$D$ 妙啊(蒟蒻听完评讲后的感言)!
题目:给你一颗树,求这些树上的节点权值两两异或和(所有点对路径上的异或和)。注意 $(x,x)$ 也是点对。
这个题没法像之前的套路性找最短路径和问题一样,因为异或没有分配律。
既然异或是二进制运算,那么对于每一位都进行一次求解,则原来的节点权值只有 $0$ 和 $1$ 两种。考虑树形dp。
$dp_{u,0/1}$ 表示在以 $u$ 为根节点的子树中,经过根节点 $u$,且不同时经过 $u$ 的两个以上的儿子的路径,异或和为 $0/1$ 的数量。
注意边算 $dp_{u,0/1}$ 边统计答案。乘法原理将两个子树路径条数乘起来即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <cstdio> #include <vector> #define int long long int dp[100005 ][23 ][2 ], val[100005 ], ans;std::vector<int > sons[100005 ]; void dfs (const int u, const int fa) { for (int i (0 ); i <= 22 ; ++ i) if (val[u] & 1LL << i) dp[u][i][1 ] = 1 ; else dp[u][i][0 ] = 1 ; for (int v : sons[u]) if (v != fa) { dfs (v, u); for (int i (0 ); i <= 22 ; ++ i) { bool f (val[u] & 1 << i) ; ans += (dp[u][i][0 ] * dp[v][i][1 ] + dp[u][i][1 ] * dp[v][i][0 ]) << i; dp[u][i][0 ] += dp[v][i][f]; dp[u][i][1 ] += dp[v][i][!f]; } } } signed main () { int n; scanf ("%lld" , &n); for (int i (1 ); i <= n; ++ i) scanf ("%lld" , val + i), ans += val[i]; for (int i (1 ); i < n; ++ i) { int u, v; scanf ("%lld%lld" , &u, &v); sons[u].push_back (v), sons[v].push_back (u); } dfs (1 , -1 ); printf ("%lld\n" , ans); return 0 ; }
$E$ 之前做过和这道题有那么一点点相似的题,但是那题太难了(所以这就是你做不出这题的理由?)。
首先 $1\le a_i\le 50$ 显然有鬼对吧,正常情况直接给到1e9。也就是题目自动帮你离散化了亿下。
然后仍然没有头绪。挖掘一下区间反转的本质。其实就是两两交换了若干个数(当然数的位置有要求)。
想到这一步这题就和之前那题有一点相似了(想到这一步了这题甚至还简单一些)。
$dp_{l,r,d,u}$ 表示 $[l,r]$ 区间的数,经过若干次合法的交换,构成一个最大值大于 $u$,最小值小于 $d$ 的最长不下降子序列长度。
转移非常套路。$dp_{l,r,d,u}=max(dp_{l,r,d+1,u},dp_{l,r,d,u-1},dp_{l+1,r,d,u}+(A_l=d),dp_{l,r-1,d,u}+(A_r=u),dp_{l+1,r-1,d,u}+(A_l=u)+(A_r=d))$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <cstdio> inline void max (int &x, const int y) {if (x < y) x = y;}int A[51 ], dp[51 ][51 ][51 ][51 ];bool vis[51 ][51 ][51 ][51 ];int dfs (const int l, const int r, const int d, const int u) { if (l > r) return 0 ; if (l == r) return d <= A[l] && A[l] <= u; if (d > u) return 0 ; if (vis[l][r][d][u]) return dp[l][r][d][u]; vis[l][r][d][u] = true ; int &ans (dp[l][r][d][u]) ; max (ans, dfs (l, r, d + 1 , u)); max (ans, dfs (l, r, d, u - 1 )); max (ans, dfs (l + 1 , r, d, u) + (A[l] == d)); max (ans, dfs (l, r - 1 , d, u) + (A[r] == u)); max (ans, dfs (l + 1 , r - 1 , d, u) + (A[l] == u) + (A[r] == d)); return ans; } int main () { int n; scanf ("%d" , &n); for (int i (1 ); i <= n; ++ i) scanf ("%d" , A + i); printf ("%d" , dfs (1 , n, 1 , 50 )); return 0 ; }