VP Educational Codeforces Round 28


A. Curriculum Vitae

题意:删去最少的数,使得任意一个1后面没有0。

记录前缀0个数和后缀1个数。然后枚举即可。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::vector<int> zero(n + 1), one(n + 2);
    for (int i = 0; i < n; ++ i) {
    	zero[i + 1] = zero[i] + (a[i] == 0);
    }

    for (int i = n - 1; i >= 0; -- i) {
    	one[i + 1] = one[i + 2] + (a[i] == 1);
    }

    int ans = zero[n];
    for (int i = 1; i <= n; ++ i) {
    	ans = std::max(ans, zero[i - 1] + one[i]);
    }

    std::cout << ans << "\n";
}

B. Math Show

题意:一个任务有\(k\)个子任务,每个子任务需要时间去完成。完成一个子任务加一分,如果你完整完成了一个任务就可以多加一分。总共有\(n\)个这样的任务。求最大得分。

首先肯定按时间从小到大排序。
\(n, k\)都比较小,可以枚举做完前\(i-1\)个任务目前做到了第\(j\)个任务,然后后面的任务都只做不超过\(k\)个任务的最大得分。

点击查看代码
void solve() {
    int n, k, m;
    std::cin >> n >> k >> m;
    std::vector<int> a(k);
    for (int i = 0; i < k; ++ i) {
    	std::cin >> a[i];
    }

    std::sort(a.begin(), a.end());

    auto get = [&](int n, int i, int m) -> int {
    	int res = 0;
    	for (int j = 0; j <= i; ++ j) {
    		int cnt = std::min(n, m / a[j]);
    		res += cnt;
    		m -= cnt * a[j];
    	}

    	return res;
    };

    int ans = 0, sum = 0;
    for (int i = 0; i < k; ++ i) {
    	ans = std::max(ans, get(n, i, m));
    }

    for (int i = 0; i < n; ++ i) {
    	for (int j = 0; j < k; ++ j) {
    		if (m < a[j]) {
    			break;
    		}

    		m -= a[j];
    		sum += 1;
    		if (j == k - 1) {
    			++ sum;
    		}

    		for (int l = 0; l < k; ++ l) {
    			ans = std::max(ans, sum + get(n - 1 - i, l, m));
    		}
    	}
    }

    std::cout << ans << "\n";
}

C. Four Segments

题意:把数组分成四部分,其和为加上第一部分的和减去第二部分的和加上第三部分的和减去第四部分的和。有些部分可以是空的,但这四个部分必须包含整个数组。求和最大。

枚举三个断点\(i, j, k\)复杂度是立方的,无法接受。但发现如果固定了\(j\),那么\(i, k\)直接互不影响,那么可以求一个前面的最大值和一个后面的最大值就行。注意下标的取值。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::vector<i64> sum(n + 2);
    for (int i = 0; i < n; ++ i) {
    	sum[i + 1] = sum[i] + a[i];
    }

    i64 ans = -1e18;
    int ansi = 0, ansj = 0, ansk = 0;
    for (int j = 1; j <= n + 1; ++ j) {
    	int k = j;
    	i64 max_suf = -1e18;
    	for (int r = j + 1; r <= n + 1; ++ r) {
    		if (sum[r - 1] - sum[j] - (sum[n] - sum[r - 1]) > max_suf) {
    			max_suf = sum[r - 1] - sum[j] - (sum[n] - sum[r - 1]);
    			k = r;
    		}
    	}

    	int i = j;
    	i64 max_pre = -1e18;
    	for (int l = 1; l <= j + 1; ++ l) {
    		if (sum[l - 1] - (sum[j] - sum[l - 1]) > max_pre) {
    			max_pre = sum[l - 1] - (sum[j] - sum[l - 1]);
    			i = l;
    		}
    	}

    	if (max_suf + max_pre > ans) {
    		ans = max_suf + max_pre;
    		ansi = i, ansj = j, ansk = k;
    	}
    }

    // std::cout << ans << "\n";
    std::cout << ansi - 1 << " " << ansj << " " << ansk - 1 << "\n";
}

D. Monitor

题意:一个矩阵里给出一些点坏掉的时间,求最早是什么时候使得有一个\(k\times k\)的子矩阵都是坏点。

对每行进行单调队列,记\(b[i][j]\)\((i, j)\)后面\(k\)\(a[i][j]\)的最大值,这是经典的单调队列问题。
然后对每列求一个\(c[i][j]\),表示\((i, j)\)往下\(k\)个的\(b[i][j]\)的最大值。
那么\(c[i][j]\)就表示以\((i, j)\)为左上角的\(k\times k\)矩阵的最大值。

点击查看代码
void solve() {
    int n, m, k, q;
    std::cin >> n >> m >> k >> q;
    const int inf = 2e9;
    std::vector a(n, std::vector<int>(m, inf));
    for (int i = 0; i < q; ++ i) {
    	int x, y, t;
    	std::cin >> x >> y >> t;
    	-- x, -- y;

    	a[x][y] = t;
    }


    std::vector b(n, std::vector<int>(m));
    std::vector c(n, std::vector<int>(m));
    int ans = inf;
    for (int i = 0; i < n; ++ i) {
    	std::vector<int> q(m + 1);
    	int hh = 0, tt = -1;
    	for (int j = m - 1; j >= 0; -- j) {
    		while (hh <= tt && q[hh] - j >= k) {
    			++ hh;
    		}

    		while (hh <= tt && a[i][q[tt]] <= a[i][j]) {
    			-- tt;
    		}

    		q[ ++ tt] = j;
    		b[i][j] = a[i][q[hh]];
    	}
    }

    for (int j = 0; j < m; ++ j) {
    	std::vector<int> q(n + 1);
    	int hh = 0, tt = -1;
    	for (int i = n - 1; i >= 0; -- i) {
    		while (hh <= tt && q[hh] - i >= k) {
    			++ hh;
    		}

    		while (hh <= tt && b[q[tt]][j] <= b[i][j]) {
    			-- tt;
    		}

    		q[ ++ tt] = i;
    		c[i][j] = b[q[hh]][j];
    	}
    }

    for (int i = 0; i + k - 1 < n; ++ i) {
    	for (int j = 0; j + k - 1 < m; ++ j) {
    		ans = std::min(ans, c[i][j]);
    	}
    }

    if (ans == inf) {
    	ans = -1;
    }

    std::cout << ans << "\n";
}

E. Chemistry in Berland

题意:给你一棵树,每个节点有一个已有材料数和需要材料数,父节点可以用自己\(k_i\)的材料的代价给儿子\(x_i\)一个单位的材料。每个点可以用1材料的代价给父节点1材料的代价。要让每个点的材料数大于等于需要的材料数。求是否可行。

这题并没有明确说是一棵树,只是说每个材料可以互相转换,然后也不说原因,就固定给你\(n-1\)个转换关系。那么互相转换意味着每个点都联通,同时只有\(n-1\)条边,那就是树了。
那么就是简单的树形\(dp\),优先用儿子多出来的材料给父亲,否则如果儿子需要材料,就只能靠父亲给了。最后如果根节点能满足需求,就可行。
注意这题会爆longlong,甚至用int128也不行,只能用longdouble。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<i64> a(n), b(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    }

    std::vector<std::vector<std::pair<int, int>>> adj(n);
    for (int i = 1; i < n; ++ i) {
    	int x, k;
    	std::cin >> x >> k;
    	-- x;
    	adj[x].push_back({x, k});
    }

    std::vector<i64> f(n);
    for (int i = 0; i < n; ++ i) {
    	f[i] = a[i] - b[i];
    }

    auto dfs = [&](auto self, int u, int fa) -> void {
    	for (auto & [v, w] : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		self(self, v, u);
    		if (f[v] < 0) {
    			f[u] += f[v] * w;
    		} else {
    			f[u] += f[v];
    		}
    	}
    };

    dfs(dfs, 0, -1);
    if (f[0] >= 0) {
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

F. Random Query

题意:给你一个数组,随机选一个区间,使得值为区间不同数的个数,求期望。

我们令区间每个数里第一个出现的数产生贡献,如果\(i\)产生了贡献,那么记\(pre_{a_i}\)\(a_i\)前一次出现的位置,那么\(i\)产生贡献的区间数是\((i - pre_i) \times (n - i + 1)\)。按照题意,每个\((l, r)\)会被算两次,那么就是\(2(i - pre_i) \times (n - i + 1)\),但要注意如果\(l = r\)则只算一次,所以最终要减去\(n\)。然后除以总个数\(n^2\)

点击查看代码
void solve() {
   	int n;
   	std::cin >> n;
   	std::vector<int> a(n);
   	for (int i = 0; i < n; ++ i) {
   		std::cin >> a[i];
   	}

   	const int N = 1e6 + 5;
   	double ans = 0;
   	std::vector<int> pre(N, -1);
   	for (int i = 0; i < n; ++ i) {
   		ans = ans + (i64)(i - pre[a[i]]) * (n - i) * 2;
   		pre[a[i]] = i;
   	}	

   	ans -= n;

   	std::cout << std::fixed << std::setprecision(12);

   	std::cout << ans / (1.0 * n * n) << "\n";
}
posted @ 2025-03-09 15:55  maburb  阅读(12)  评论(0)    收藏  举报