codeforeces近日题目小结
upd:2019-12-20
题目源自codeforces的Round_551
Round_551/F:
牛逼的dp
upd:2019-12-14
题目源自codeforces的Round_552
Round_552/F:
这个题一开始按照背包的经典dp转移去写,外面枚举物品,里面枚举状态,就WA
仔细思考之后发现这个问题里,把sp offer看作背包问题中的物品
两个不同物品放入背包的先后顺序,对答案是有影响的
为了避免这种影响,应该外层枚举状态,内层枚举物品。这样就可了
Round_552/G:
智力题。先处理两个数相同 lcm(x, x) = x的情况。然后再做下面。
假设ai和aj为最优解,那么有g=gcd(ai, aj)
枚举g,找到g的倍数中在a里出现过的最小的两个不同数x*g和y*g,用x*y*g更新答案即可
由于我们枚举g从1-1e7,所以一定能够枚举到gcd(ai, aj),此时最优解一定会更新答案,所以保证正确
upd:2019-12-13
题目源自codeforces的Round_602_div1+2, Round_603_div2, Educational_Round_77
Round_603/F:
考虑一个经典dp状态定义, dp[i][j]代表上面一个树的最后一个被选取的叶节点编号是i
下面的树最后一个被选取的叶节点编号是j且 i != j
那么我们需要考虑第 max(i, j) + 1 个点是选择上下哪棵树的点
这个代价其实就是比较好求的了
Round_602_div1/E:
我直接参考的最短的代码
一个比较牛逼的构造,正确性自己手画一下是可以证明的
我先证明了相邻两行必然不等,然后证明的 1 <= i < j <= n 必有第i行和第j行不等
然后证明1 <= i <= n 必有第i行和第 n+1 行不等
Round_603_div1/F:
一个比较牛逼的分治
Educational_Round_77/F:
一个比较牛逼的树上计数
upd:2018-11-25
题目源自codeforeces的三场contest
contest/1043+1055+1076
目前都是solved 6/7,都差了最后一题
简单题:
contest/1043/E:
先不考虑m个限制,求出每个人与其他所有人组队的情况下这个人获得的分数
对于当前的 i ,如果他与 j 组队时 i 做第一题,则有 xi + yj <= xj + yi
即 xi - yi <= xj - yj,排序累加计算即可
contest/1055/C:
注意到 la 与 lb 的差距会由于 t0 和 t1 而变化 k*gcd(t0, t1), k为系数
所以肯定想让 la 和 lb 离得尽量近,重合部分也就越大
能让两个位置重合就重合,不能重合就在那个位置前后蹭蹭就行了
contest/1076/D:
考虑最短路的dij算法,发现那些在最短路上的边形成了一棵树
所以直接跑堆优化dij
contest/1076/E:
kdtree模板题,变成二维空间操作,一维dep,一维dfn
二维空间矩形加+单点查询?考虑差分变为,单点加+前缀和
查询是在所有操作完成后,所以直接把操作和查询混在一起
按照第一关键字x,第二关键字y排序,排序后直接树状数组维护即可
O(nlogn)
思维僵化,有个O(n)做法,使用差分数组 f[ ]
把每个操作(v, d, x)挂到节点 v 上
然后一遍dfs,在到达v的时候对于节点上每个操作
f[dep[v]] += x, f[dep[v] + d + 1] -= x
dfs 回到点 v 父亲之前再做逆操作
对f[ ]求[1, dep[u]]的前缀和即为点 u 的答案
我傻逼了好久的题目:
contest/1055/D:
先对于那些w[i] != v[i]的所有串
求出他们共同的核心替换部分(必须替换并且长度一致)
然后为了不让无辜串也被替换所以要尝试将该串尽量向左右拓展
最后求出来替换串 s -> t 之后再验证,验证一开始想的太简单了
w[i] = v[i]的串,都满足w[i].find(s) == 0是不足够的
还会有别的情况!
简单暴力就是对n个串w[i]都find一下s,第一次找到就替换成 t
然后新串与v[i]对比即可
1 #include <bits/stdc++.h> 2 3 #define lb(x) (x&(-x)) 4 5 typedef long long ll; 6 7 using namespace std; 8 9 const int N = 5010; 10 11 string s = "", t; 12 13 int n, flag[N]; 14 15 string a[N], b[N]; 16 17 int nex[N], l[N], r[N]; 18 19 vector <int> lt; 20 21 void calc_next() { 22 nex[0] = -1; 23 for (int i = 1; i < s.size(); i ++) { 24 int j = nex[i - 1]; 25 while (j != -1 && s[j + 1] != s[i]) j = nex[j]; 26 if (s[j + 1] == s[i]) nex[i] = j + 1; 27 else nex[i] = -1; 28 } 29 } 30 31 void kmp(string &st) { 32 for (int i = 0, j = -1; i < st.size(); i ++) { 33 while (j != -1 && s[j + 1] != st[i]) j = nex[j]; 34 if (s[j + 1] == st[i]) { 35 j ++; 36 if (j + 1 == s.size()) { 37 st = st.substr(0, i + 1 - s.size()) + t + st.substr(i + 1); 38 return; 39 } 40 } 41 } 42 } 43 44 int main() { 45 ios::sync_with_stdio(false); 46 cin >> n; 47 for (int i = 1; i <= n; i ++) cin >> a[i]; 48 for (int i = 1; i <= n; i ++) cin >> b[i]; 49 for (int i = 1; i <= n; i ++) { 50 l[i] = -2, r[i] = -2; 51 for (int j = 0; j < a[i].size(); j ++) { 52 if (a[i][j] != b[i][j]) { 53 if (l[i] == -2) l[i] = j; 54 r[i] = j; 55 } 56 } 57 if (l[i] == -2) continue; 58 if (s == "") s = a[i].substr(l[i], r[i] - l[i] + 1), t = b[i].substr(l[i], r[i] - l[i] + 1); 59 else if (s != a[i].substr(l[i], r[i] - l[i] + 1) || t != b[i].substr(l[i], r[i] - l[i] + 1)) { 60 cout << "NO"; 61 return 0; 62 } 63 lt.push_back(i); 64 } 65 while (1) { 66 int flag = 1; 67 for (int i : lt) { 68 l[i] --; 69 if (l[i] < 0) { 70 flag = 0; 71 break; 72 } 73 } 74 if (!flag) break; 75 char ch = a[lt[0]][l[lt[0]]]; 76 for (int i : lt) { 77 if (ch != a[i][l[i]]) { 78 flag = 0; 79 break; 80 } 81 } 82 if (!flag) break; 83 s = ch + s; 84 t = ch + t; 85 } 86 while (1) { 87 int flag = 1; 88 for (int i : lt) { 89 r[i] ++; 90 if (r[i] >= a[i].size()) { 91 flag = 0; 92 break; 93 } 94 } 95 if (!flag) break; 96 char ch = a[lt[0]][r[lt[0]]]; 97 for (int i : lt) { 98 if (ch != a[i][r[i]]) { 99 flag = 0; 100 break; 101 } 102 } 103 if (!flag) break; 104 s += ch; 105 t += ch; 106 } 107 calc_next(); 108 for (int i = 1; i <= n; i ++) { 109 kmp(a[i]); 110 if (a[i] != b[i]) { 111 cout << "NO"; 112 return 0; 113 } 114 } 115 cout << "YES\n" << s << '\n' << t; 116 return 0; 117 }
其他题目:
contest/1055/F:
树上异或路径和,把每个点的权值变为到根的异或路径和
异或路径和就变为了两点的权值异或和
这个题如果问有多少条路径异或和<=x
就可以直接树分治+trie树,O(nlogn^2)
但是问排名为k的,套个二分O(nlogn^3),gg
直接在trie树上搞,从高到低考虑当前位取0的结果个数
超过了k,则ans当前位取0,否则当前位取1
空间O(62*n)会爆MLE,直接一层一层的构建trie树
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 const int N = 2e6 + 10; 6 7 int cnt, t, sz[N]; 8 9 int n, a[N], b[N], ch[N][2]; 10 11 long long s, k, ans, v[N]; 12 13 int pos(int x, int y) { 14 return ch[x][y] ? ch[x][y] : ch[x][y] = ++ cnt; 15 } 16 17 int main() { 18 ios::sync_with_stdio(false); 19 cin >> n >> k; 20 for (int p, i = 2; i <= n; i ++) 21 cin >> p >> v[i], v[i] ^= v[p]; 22 for (int i = 1; i <= n; i ++) 23 a[i] = b[i] = 1; 24 for (int j = 61; ~j; j --) { 25 for (int i = 1; i <= cnt; i ++) ch[i][0] = ch[i][1] = sz[i] = 0; 26 s = t = cnt = 0; 27 for (int i = 1; i <= n; i ++) sz[a[i] = pos(a[i], v[i] >> j & 1)] ++; 28 for (int i = 1; i <= n; i ++) s += sz[ch[b[i]][v[i] >> j & 1]]; 29 if (s < k) ans |= 1ll << j, k -= s, t = 1; 30 for (int i = 1; i <= n; i ++) b[i] = ch[b[i]][(v[i] >> j & 1) ^ t]; 31 } 32 cout << ans; 33 return 0; 34 }
几个DP题目:
contest/1043/F:
给出n个数,问最少选几个数可以使得gcd=1
考虑最优解集合,先拿出第一个数
然后依次拿出其他数跟它求gcd,那么gcd一定是逐次减小的
并且每次约去的质因数都是不同的(不然这个数没有意义不会出现在最优集合里)
ai <= 3e5,可以求出ans如果存在一定 ans <= 7
设计dp[i][j]代表去除 i 个不同数字使得他们gcd为 j 的方案数有多少种
i 从小到大枚举, for i 1 -> 7
dp[i][j] 考虑容斥求出,取 i 个数的gcd为 j 的倍数的方案数
减去 gcd 为 j*2, j*3, j*4 的方案数即为我们需要的方案数了
dp[i][1] != 0,则取 i 个数能 gcd = 1
O(k*nlogn),k为系数,最大是 7
考虑中间需要组合数,而C(n, 7)会爆long long
但注意到我们的不需要结果的具体方案数,只想知道dp[i][1] != 0
所以考虑直接模大质数即可,自然溢出也可以
再不相信就直接取两个大质数跑两次验证
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 const int N = 3e5 + 10; 6 7 const int Mod = 1e9 + 7; 8 9 int n, m, a[N], b[N]; 10 11 int c[N], dp[N], cnt[N]; 12 13 int main() { 14 ios::sync_with_stdio(false); 15 cin >> n; 16 for (int i = 1; i <= n; i ++) 17 cin >> a[i], m = max(m, a[i]), b[a[i]] ++; 18 for (int i = 1; i <= m; i ++) { 19 for (int j = i; j <= m; j += i) 20 cnt[i] += b[j]; 21 c[i] = 1; 22 } 23 for (int k = 1; k < 8; k ++) { 24 for (int i = m; i; i --) { 25 dp[i] = (c[i] = 1ll * c[i] * (cnt[i] + 1 - k) % Mod); 26 for (int j = i << 1; j <= m; j += i) 27 dp[i] = (dp[i] - dp[j]) % Mod; 28 } 29 if (dp[1] != 0) { 30 printf("%d\n", k); 31 return 0; 32 } 33 } 34 puts("-1"); 35
contest/1055/E:
这题面有毒啊,应该用set不是multiset啊
直接二分答案x进行验证
是否可以取出m个区间,使得m个区间组成的set里<=x的数字>=k个
dp[i][j]代表假设总区间只有[1, i]
选择了 j 个区间后,最多能包含多少个<=x的数字
枚举 i, 考虑dp[i][j],只有包含 i 和不包含 i
不包含 i -> dp[i - 1][j]
包含 i, 找到所有包含i的区间里最小的左端点L -> dp[L - 1][j - 1] + count(ai <= x for i in [L, i])
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 const int N = 1510; 6 7 int n, m, s, k; 8 9 int a[N], l[N], r[N], dp[N][N]; 10 11 bool check(int x) { 12 memset (dp, 0, sizeof dp); 13 for (int i = 1; i <= n; i ++) { 14 int pos = i + 1, sum = 0; 15 for (int j = 1; j <= s; j ++) 16 if (l[j] <= i && i <= r[j]) 17 pos = min(pos, l[j]); 18 for (int j = pos; j <= i; j ++) 19 sum += a[j] <= x; 20 for (int j = 1; j <= m; j ++) 21 dp[i][j] = max(dp[i - 1][j], dp[pos - 1][j - 1] + sum); 22 } 23 return dp[n][m] >= k; 24 } 25 26 int main() { 27 ios::sync_with_stdio(false); 28 int L = 1e9, R = 1, mid, ans = -1; 29 cin >> n >> s >> m >> k; 30 for (int i = 1; i <= n; i ++) 31 cin >> a[i], L = min(L, a[i]), R = max(R, a[i]); 32 for (int i = 1; i <= s; i ++) 33 cin >> l[i] >> r[i]; 34 while (L <= R) { 35 if (check(mid = L + R >> 1)) ans = mid, R = mid - 1; 36 else L = mid + 1; 37 } 38 cout << ans; 39 return 0; 40 }
contest/1076/F:
dp[i][j]代表第 i 页以 type j 结尾的话,最少结尾是几个连续的type j
因为考虑当前页以type x结尾的话
如果下一页的全局最优答案是以type x开始的话,那肯定希望当前页结尾的x越少越好
如果不是type x开始的话,那么当前页只要以x结尾,多少个都无所谓啦
所以我们需要这个状态!
min(dp[n][0], dp[n][1])即为答案
转移就贪心转移
1 #include <bits/stdc++.h> 2 3 #define lb(x) (x&(-x)) 4 5 using namespace std; 6 7 const int N = 3e5 + 10; 8 9 typedef long long ll; 10 11 ll n, k, a[N][2], dp[N][2]; 12 13 int main() { 14 ios::sync_with_stdio(false); 15 cin >> n >> k; 16 for (ll i = 1; i <= n; i ++) cin >> a[i][0]; 17 for (ll i = 1; i <= n; i ++) cin >> a[i][1]; 18 for (ll s0, min0, s1, min1, i = 1; i <= n; i ++) { 19 dp[i][0] = dp[i][1] = k + 1; 20 if (dp[i - 1][0] <= k) { 21 s0 = dp[i - 1][0] + a[i][0]; 22 min1 = (s0 + k - 1) / k - 1; 23 if (a[i][1] >= min1 && a[i][1] <= (a[i][0] + 1) * k) { 24 if (a[i][1] == min1) dp[i][0] = min(dp[i][0], s0 - k * min1); 25 else if (a[i][1] > a[i][0] * k) dp[i][1] = min(dp[i][1], a[i][1] - a[i][0] * k); 26 else dp[i][0] = dp[i][1] = 1; 27 } 28 } 29 if (dp[i - 1][1] <= k) { 30 s1 = dp[i - 1][1] + a[i][1]; 31 min0 = (s1 + k - 1) / k - 1; 32 if (a[i][0] >= min0 && a[i][0] <= (a[i][1] + 1) * k) { 33 if (a[i][0] == min0) dp[i][1] = min(dp[i][1], s1 - k * min0); 34 else if (a[i][0] > a[i][1] * k) dp[i][0] = min(dp[i][0], a[i][0] - a[i][1] * k); 35 else dp[i][0] = dp[i][1] = 1; 36 } 37 } 38 } 39 cout << (dp[n][0] <= k || dp[n][1] <= k ? "YES" : "NO"); 40 return 0; 41 }