Codeforces Round #787 (Div. 3) F, G题题解
作者:互联网
Codeforces Round #787 (Div. 3)
F. Vlad and Unfinished Business ( \(\color{#AAF}{1800}\) )
题意
给了点数为 \(n\) 的树,和 \(k\) 个必须到达的点,出发的起点 \(x\) 和 到达终点 \(y\),边权为 \(1\) ,询问从起点经过指定的
\(k\) 个点,最后到达 \(y\) 的最短路径花费, \(k\) 个点的访问顺序可以任意。
数据范围
\(1\leq k \leq n \leq 2 * 10^5\)
思路
- 贪心地想,对于所有任务点必须到,然后要返回,那么起点到这些点的路径和至少为2倍到达这些点的花费。
- 问题转化为,经过 \(k\) 个点的"最小生成树",当然这里不需要最小生成树算法,只需要知道我们不需要经过的点数即可,剩余点数为 \(x\),路径长度为 \(x-1\) 。
- 那么现在访问了所有点,要更贪心的想,从 \(x->y\) 我们是不是只用走一次就行了,先把 \(y\) 以外的点搜完,最后来搜 \(y\) 的子树即可,那么 \(path:x->y\) 只需要经过一次。
- 所以最后答案为 \(2*(必经路径上点数-1) - x->y的深度(距离)\) ,用 dfs 求子树标记的方式来解,详细见代码,时间复杂度 \(O(n)\)
Solution
#include<bits/stdc++.h>
typedef long long ll;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef unsigned long long ull;
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int N = 2e5 + 10;
int n, k, x, y, ans;
vector<int> edge[N];
int dep[N];
bool st[N];
int dfs(int u, int p){
int tot = 0;
for(auto v: edge[u]){
if(v == p) continue;
dep[v] = dep[u] + 1;
tot += dfs(v, u);
}
if(!tot && !st[u]) ans--;
return tot + (st[u] == true);
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while(T--){
cin >> n >> k;
cin >> x >> y;
ans = n;
vector<int> a(k + 1, 0);
for(int i = 1; i <= k; i++){
cin >> a[i];
st[a[i]] = true;
}
st[x] = true, st[y] = true;
for(int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
edge[u].pb(v);
edge[v].pb(u);
}
dfs(x, -1);
cout << 2 * (ans - 1) - dep[y] << endl;
for(int i = 1; i <= n; i++){
edge[i].clear();
st[i] = false;
dep[i] = 0;
}
}
return 0;
}
G. Sorting Pancakes ( \(\color{#FB5}{2300}\) )
题意
给了一个长度 \(n\) 的数组 \(a\) ,和保证为 \(m\), 每次只能将 \(a_i\) 减 1, \(a_{i-1}\; or\; a_{i+1}\) 加 1,询问最小的操作次数使 \(a\) 非严格递减。
数据范围
\(1\leq n,m \leq 250\)
思路
- 毫无疑问,一道dp题,根据数据范围,大概可以容忍 \(O(n^3)\) 级别的时空复杂度。
- 在设计状态前,务必需要一个结论:
- 对于这样的相邻移动元素的操作而言,设操作后的结果为 \(b\) 数组,总操作次数等于 \(\Sigma_{i=1}^n(|S_b[i] - S_a[i]|)\) ,对应前缀和差的绝对值的和
- 首先dp的阶段是长度 \(i\),由于数组和固定,考虑加入一维当前数组的和 \(j\) ,由于要满足递增或者递减,加入一维记录最后一个数 \(k\) ,由于需要对比大小,这个数其实是一个最值。
- 那么状态已经设计出来了:
dp[i][j][k]
表示数组前 \(i\) 位,和为 \(j\) ,最后一位最值为 \(k\) 的最小操作次数。 - 考虑如何转移方便,如果按照题意原本描述来看,转移方程是遍历前一个数 \(x,k\leq x\)
dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j - k][x] + abs(j - s[i]))
,观察发现可以使用最小后缀来优化 \(x\) 的遍历,此时预处理最小后缀复杂度为 \(O(n^3)\) - 而考虑反着做,即将数组翻转后,考虑将数组构造成非严格递增。那么 \(k\) 代表的是最后一位的最大值。从小到大枚举\(k\) 时,需要的是最小前缀,可以很方便的记录, 如下代码所示
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
int mi = 0x3f3f3f3f;
for(int k = 0; k <= m - j; k++) {
mi = min(mi, dp[i - 1][j][k]); // 转移 k 时,mi 记录了上一位数值为 [0, k] 的最小值,因此是合法的
f[i][j + k][k] = min(f[i][j + k][k], mi + abs(s[i] - abs(j + k)));
}
}
}
- 时间复杂度:将 \(n\) 和 \(m\) 同级,\(O(n^3)\) ,翻转数组的方法还没有想到如何优化成 \(O(n^2logn)\) 。
- 如果尝试采用记录后缀最小来优化,发现对于下标 \(i\) 上每个数不能超过
m / i
,否则它一定比前面的某个数大(平均值),加入循环条件中可以做到 \(O(n^2logn)\)。
- 如果尝试采用记录后缀最小来优化,发现对于下标 \(i\) 上每个数不能超过
Solution
#include<bits/stdc++.h>
typedef long long ll;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef unsigned long long ull;
#define x first
#define y second
#define pb push_back
#define mkp make_pair
#define endl "\n"
using namespace std;
const int N = 260;
int n, m;
int a[N], s[N];
int dp[N][N][N];
// 将数组翻转
// dp[i][j][k] 前i个数,和为j,最后一个数最大值为k 满足递增序列最小操作
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = n; i >= 1; i--)
cin >> a[i];
for(int i = 1; i <= n; i++)
s[i] = s[i - 1] + a[i];
memset(dp, 0x3f, sizeof dp);
dp[0][0][0] = 0; // 前0个数,和为0,数最大值为0,操作次数为0
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
int mi = 0x3f3f3f3f;
for(int k = 0; k <= m - j; k++){
mi = min(dp[i - 1][j][k], mi);
dp[i][j + k][k] = min(dp[i][j + k][k], mi + abs((j + k) - s[i]));
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 0; i <= m; i++)
ans = min(ans, dp[n][m][i]);
cout << ans << endl;
return 0;
}
标签:typedef,787,int,题解,cin,long,Div,dp,define 来源: https://www.cnblogs.com/Roshin/p/cfround787div3.html