1. 程式人生 > 其它 >Codeforces Round #787 (Div. 3) F, G題題解

Codeforces Round #787 (Div. 3) F, G題題解

Codeforces題解彙總

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}\) )

參考題解: Ximena, 嚴格鴿

題意
給了一個長度 \(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)\)

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;
}