https://codeforces.com/contest/1120
A. Diana and Liana
题意:m个数,k个一组,取前n组,要求删除一些数,使得取的n组中至少一组满足存在给定的需要的数。删除的数个数任意,但必须剩下至少n组。
枚举
遍历每个位置,考虑从它开始构造一组满足条件的数。找到它开始的最短的满足含有所有需要的数的区间。再考虑删除前面的数,使得这个位置成为一组的开头,且在n组内,再删除组内的数,直到个数为k。判断删除后剩下至少n组,则满足条件,输出。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <bits/stdc++.h> using namespace std;#define ll long long const int INF = 0x3f3f3f3f ;const int N = 5e5 + 10 ;int m, k, n, s;int a[N], b[N], nx[N];vector<int >vc[N]; int main () { scanf ("%d%d%d%d" , &m, &k, &n, &s); for (int i = 1 ; i <= m; i++)scanf ("%d" , &a[i]), vc[a[i]].push_back (i); for (int i = 1 ; i <= s; i++) { int x; scanf ("%d" , &x); b[x]++; } int mx = 0 ; for (int i = 1 ; i <= 500000 ; i++) { if (b[i]) { if ((int )vc[i].size () >= b[i])mx = max (mx, vc[i][b[i] - 1 ]); else { mx = INF; break ; } } } nx[1 ] = mx; for (int i = 2 ; i <= m; i++) { if (b[a[i - 1 ]]) { int p = lower_bound (vc[a[i - 1 ]].begin (), vc[a[i - 1 ]].end (), i) - vc[a[i - 1 ]].begin (); p = p + b[a[i - 1 ]] - 1 ; if (p >= (int )vc[a[i - 1 ]].size ())nx[i] = INF; else nx[i] = max (mx, vc[a[i - 1 ]][p]); mx = max (mx, nx[i]); } else nx[i] = mx; } for (int i = 1 ; i <= m; i++) { int p = 0 , t = 0 ; if (i > n*k)p = t = i - 1 - n*k; else p = t = (i - 1 ) % k; p += max (0 , nx[i] - i + 1 - k); if (m - p >= n * k) { printf ("%d\n" , p); for (int j = 1 ; j <= t; j++)printf ("%d " , j); int cnt = nx[i] - i + 1 - k; for (int j = i; j <= nx[i] && cnt > 0 ; j++) { if (b[a[j]] <= 0 ) { printf ("%d " , j); cnt--; } else b[a[j]]--; } return 0 ; } } puts ("-1" ); return 0 ; }
C. Compress String
题意:从空字符串开始,花费a加入一个字符,或者花费b加入一个字符串,且加入的字符串是前面字符串的子串。问构造出给定串的最小花费。
容易想到dp,问题在于怎么找到是前面字符串子串的串。
最重要的一点是,要发现 d p [ i ] dp[i] d p [ i ] 一定是单调非递减的 ,因为假设 d p [ i ] dp[i] d p [ i ] 由 d p [ k ] + b dp[k]+b d p [ k ] + b 得到,也就是说 s [ k + 1 , i ] s[k+1,i] s [ k + 1 , i ] 是 s [ 1 , k ] s[1,k] s [ 1 , k ] 的子串,设有一个 j < i j<i j < i , 那么 s [ k + 1 , j ] s[k+1,j] s [ k + 1 , j ] 是 s [ k + 1 , i s[k+1,i s [ k + 1 , i ] 的子串,则一定也是 s [ 1 , k ] s[1,k] s [ 1 , k ] 的子串,所以 d p [ j ] dp[j] d p [ j ] 也一定可以由 d p [ k ] + b dp[k]+b d p [ k ] + b 得到,则 d p [ j ] dp[j] d p [ j ] 一定不大于 d p [ i ] dp[i] d p [ i ] 。造成这样的原因还是因为每次操作不论长度,花费都可以一样。
所以 d p [ i ] = d p [ j ] + b dp[i]=dp[j]+b d p [ i ] = d p [ j ] + b 时,一定是 j j j 越小越好,那么就是一定要找当前串最长 的属于前面串子串的后缀。
所以 r [ i ] [ j ] r[i][j] r [ i ] [ j ] 维护串s的长为i的前缀和长为j的前缀的最长公共后缀,转移方程也容易得到。
还要注意两个串不能相交,所以遍历时不能直接用r值。
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 <bits/stdc++.h> using namespace std;#define ll long long const int INF = 0x3f3f3f3f ;const int N = 5e3 + 10 ;int n, a, b;char s[N];int dp[N], r[N][N];int main () { scanf ("%d%d%d" , &n, &a, &b); scanf ("%s" , s + 1 ); memset (dp, 0x3f , sizeof (dp)); dp[1 ] = a; for (int i = 2 ; i <= n; i++) { dp[i] = dp[i - 1 ] + a; for (int j = 1 ; j < i; j++) { r[i][j] = (s[i] == s[j] ? r[i - 1 ][j - 1 ] + 1 : 0 ); int len = min (r[i][j], i - j); dp[i] = min (dp[i], dp[i - len] + b); } } printf ("%d\n" , dp[n]); return 0 ; }
D. Power Tree
题意:给定一棵1为根的树,每点有点权,初始给每个叶子赋值,并选一些点组成点集。每次操作选择点集中一点,把它子树中的叶子的值都加或减任意数,使得不论给每个叶子赋初值多少,都能通过有限次操作使得最终所有叶子值为0。每次操作的花费为操作的点的点权,要求输出最小花费,对于一个点,若存在一种最优方案包含它,则输出。
树形dp/最小生成树+差分
d p [ u ] [ 0 / 1 ] dp[u][0/1] d p [ u ] [ 0/1 ] 表示节点u的子树中所有叶子都被覆盖/未都被覆盖。
都被覆盖,有两种可能:选节点u且恰好一个子节点未被覆盖,或者所有子节点都被覆盖。
未被覆盖,则恰好一个子节点未被覆盖。
最终结果为 d p [ 1 ] [ 0 ] dp[1][0] d p [ 1 ] [ 0 ] 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void dfs (int u, int _fa) { if ((int )G[u].size () == 1 && u != 1 ) { dp[u][0 ] = a[u]; dp[u][1 ] = 0 ; return ; } for (int v : G[u]) { if (v == _fa)continue ; dfs (v, u); sum[u] += dp[v][0 ]; } dp[u][0 ] = sum[u]; dp[u][1 ] = inf; for (int v : G[u]) { if (v == _fa)continue ; dp[u][0 ] = min (dp[u][0 ], sum[u] - dp[v][0 ] + dp[v][1 ] + a[u]); dp[u][1 ] = min (dp[u][1 ], sum[u] - dp[v][0 ] + dp[v][1 ]); } }
但是要输出方案实在是太烦了,最后还是没写出来。
把所有叶子按照dfs序排序,则对于节点u操作相当于对于一个区间内的叶子同时操作。
所以要考虑差分,每次在差分数组上 L L L 上 + t +t + t ,在 R + 1 R+1 R + 1 上$ -t$,最终必须要能够对任意两个 ( L , R + 1 ) (L,R+1) ( L , R + 1 ) 操作,所以再把每次操作看作从 L L L 向 R + 1 R+1 R + 1 连一条边权为 a [ i ] a[i] a [ i ] 的无向边,最终必须要任意连点联通,求最小花费,也就是最小生成树。
再考虑输出方案。如果一条边可能在某个最小生成树上,则它代表的操作的点就要输出。
把边排序后,相同边权的边如果加入后不会有环,则可能在最小生成树上,并且求最小生成树时相同边权的边加入的顺序可能影响最终的连边,但是不会影响集合的联通性,即无论怎么加边,应该在同一集合的点一定在同一集合,所以加边顺序不会影响后面的判断。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;#define ll long long const int INF = 0x3f3f3f3f ;const ll inf = 0x3f3f3f3f3f3f3f3f ;const int N = 2e5 + 10 ;int n;int a[N];ll ans; vector<int >G[N]; int L[N], R[N], clk, ok[N], cnt;struct E { int u, v, w, rt; bool operator <(const E& a)const { return w < a.w; } }; vector<E>es; void dfs (int u, int _fa) { L[u] = INF, R[u] = 0 ; if ((int )G[u].size () == 1 && u != 1 ) { L[u] = R[u] = ++clk; } for (int v : G[u]) { if (v == _fa)continue ; dfs (v, u); L[u] = min (L[u], L[v]); R[u] = max (R[u], R[v]); } es.push_back (E{ L[u],R[u] + 1 ,a[u],u }); } int par[N];void init (int n) { for (int i = 0 ; i <= n; i++)par[i] = i; }int find (int x) { return par[x] == x ? x : par[x] = find (par[x]); }void uni (int x, int y) { par[find (x)] = find (y); }int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i++)scanf ("%d" , &a[i]); for (int i = 1 ; i < n; i++) { int u, v; scanf ("%d%d" , &u, &v); G[u].push_back (v); G[v].push_back (u); } dfs (1 , 0 ); init (clk); sort (es.begin (), es.end ()); for (int i = 0 , j = 0 ; i < n; i = j) { while (j < n&&es[j].w == es[i].w)j++; for (int k = i; k < j; k++) { if (find (es[k].u) != find (es[k].v))ok[es[k].rt] = 1 , cnt++; } for (int k = i; k < j; k++) { if (find (es[k].u) != find (es[k].v)) { uni (es[k].u, es[k].v); ans += es[k].w; } } } printf ("%lld %d\n" , ans, cnt); for (int i = 1 ; i <= n; i++)if (ok[i])printf ("%d " , i); puts ("" ); return 0 ; }