VP Educational Codeforces Round 158 (Rated for Div. 2)

A. Line Trip

题意:你要从0x,然后再从x0,路上有一些加油站,每行驶一单位消耗一格油,到了加油家会加满油,问油箱至少得多大。

显然油箱要支撑我们行驶完任意两个相邻点之间的路程,注意最后xan的路径因为要返回所以要算两遍。

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

    int max = std::max(a[0], 2 * (x - a.back()));
    for (int i = 1; i < n; ++ i) {
    	max = std::max(max, a[i] - a[i - 1]);
    }
    std::cout << max << "\n";
}

B. Chip and Ribbon

题意:n个数,每次你可以选择一段区间让这个区间的数都加1,问最少几次可以让每个数恰好是目标值。

考虑一段升序的区间[l,r]要选几次,显然是最大值,那么对于这个区间后面的数可以被我们拉进来选几次,对于后面的一个i,显然它可以被影响min{al+1,...ai}次,那么我们枚举每一个升序的连续段,记录每个位置前面的升序段操作数对他的影响,然后在每个升序段的最大值处累加答案就行。

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

   	i64 ans = 0, min = 0;
   	for (int i = 0; i < n; ++ i) {
   		if (a[i] > a[i + 1]) {
   			ans += a[i] - min;
   			min = a[i + 1];
   		}
   	}

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

C. Add, Divide and Floor

题意:给你一个数组,每次选择一个数x让数组里每个数都加x后向下取整,问最少几次能让数组里的数都相等。

因为所有数都是进行同样的操作,那么我们应该关注最大值变成目标值需要几次,一个可行方案是都变成0,这样的操作数是logmax+1,但我们注意到如果每次可以维持min不变,那么max变成min只需要logmaxmin+1次,如何维持min不变?我们每次都选x = min就行了。

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

    int min = *std::min_element(a.begin(), a.end());
    int max = *std::max_element(a.begin(), a.end());
    int ans = 0;
    while (max != min) {
    	max = max + min >> 1;
    	++ ans;
    }

    std::cout << ans << "\n";
    if (ans <= n) {
    	for (int i = 1; i <= ans; ++ i) {
    		std::cout << min << " \n"[i == ans];
    	}
    }
}

D. Yet Another Monster Fight

题意:有n个怪物,你可以选一个怪物作为目标,如何它会受到x点伤害,然后每次都会随机往左边或者右边扩展,第i次扩展到的怪物会受到xi点伤害。要一次杀死所有怪物,让你选目标,x最小可以是多少。

假设我们选择了i为目标,那么对于j(j>i)的怪物,最差是一直把左边打完再走右边,受到的最小可能伤害是xj1,如果是j(j<i),那么会受到x(nj)点伤害。那么具体的,如果i是受击目标,它会受到x的伤害,那么x要大于等于ai,如果i在目标右边,会受到xj1伤害,得x>=ai+j+1,如果在左边,可以得x>=ai+nj。那么我们记录每个区间里每个点作为右边点得最大值,和作为左边的点的最大值,就可以枚举目标位置三个情况里取最大值。
发现这两个区间一定是前缀或后缀,可以一个数组维护,但我赛时没想到,直接上了线段树。

点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)

struct Node {
	int l, r;
	int max;
};

struct SegmentTree {
	std::vector<Node> tr;
	SegmentTree(int _n) {
		tr.assign(_n << 2, {});
		build(1, 1, _n);
	}

	void pushup(int u) {
		tr[u].max = std::max(tr[ls].max, tr[rs].max);
	}

	void build(int u, int l, int r) {
		tr[u] = {l, r};
		int mid = l + r >> 1;
		if (l == r) {
			return;
		}

		build(ls, l, mid); build(rs, mid + 1, r);
	}

	void modify(int u, int p, int v) {
		if (tr[u].l == tr[u].r) {
			tr[u].max = v;
			return;
		}

		int mid = tr[u].l + tr[u].r >> 1;
		if (p <= mid) {
			modify(ls, p, v);
		} else {
			modify(rs, p, v);
		}

		pushup(u);
	}

	int query(int u, int l, int r) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].max;
		}

		int mid = tr[u].l + tr[u].r >> 1;
		if (r <= mid) {
			return query(ls, l, r);
		} else if (l > mid) {
			return query(rs, l, r);
		} else {
			return std::max(query(ls, l, r), query(rs, l, r));
		}
	}
};

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    SegmentTree trl(n), trr(n);
    for (int i = 0; i < n; ++ i) {
    	trl.modify(1, i + 1, a[i] + n - i - 1);
    	trr.modify(1, i + 1, a[i] + i);
    }

    int ans = 2e9;
    for (int i = 1; i <= n; ++ i) {
    	if (i == 1) {
    		ans = std::min(ans, std::max(a[i - 1], trr.query(1, i + 1, n)));
    	} else if (i == n) {
    		ans = std::min(ans, std::max(a[i - 1], trl.query(1, 1, i - 1)));
    	} else {
    		ans = std::min(ans, std::max({a[i - 1], trr.query(1, i + 1, n), trl.query(1, 1, i - 1)}));
    	}
    }

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

E. Compressed Tree

题意:有一颗树,每个节点有权值,你可以每次选择一个叶子删掉,在你操作完之后,会删除所有有恰好两条边的点,问最后剩下的点权值和最大是多少。

考虑dp, fu表示u这棵子树里可以取到的最大值是多少,那么对于叶子就是fu=au,对于非叶节点,我们把所有子节点的f弄出来,那么我们可以只留下u这个点,值是au,也可以只留下一个最大的子节点。也可以选择两个以上子节点,那么au必须选,我们选完两个最大子节点后再把其他大于零的节点也加上就行了。那么fu就是这几个情况的最大值。
那么求出来了fu,如何统计答案?统计单个点,两个点,和一颗子树这三种情况的最大值就行了,注意统计子树时,我们不需要管父亲节点,我当时就是想着如何把父亲节点的贡献加进来,写了半天。如果我们选的点要往上走,那么总有一个祖先节点可以枚举到这种情况,所以我们只要管每个节点的子节点就行了。 注意在u这个子节点里,如果只选两个子节点,那么u这个点会被删除,不能算进答案。

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

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

    std::vector<i64> f(n, 0);
    i64 ans = 0;
    auto dfs = [&](auto self, int u, int fa) -> void {
    	std::vector<i64> b;
    	f[u] = a[u];
    	ans = std::max(ans, a[u]);
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		self(self, v, u);
    		b.push_back(f[v]);
    		ans = std::max(ans, a[u] + f[v]);
    	}

    	std::sort(b.begin(), b.end(), std::greater<i64>());
    	if (b.size() == 1) {
    		f[u] = std::max(a[u], b[0]);
    	} else if (b.size() >= 2) {
    		i64 sum = b[0] + b[1];
    		f[u] = std::max({a[u], b[0], b[1], sum + a[u]});
    		ans = std::max(ans, sum);
    		for (int i = 2; i < b.size(); ++ i) {
    			sum += b[i];
    			f[u] = std::max(f[u], sum + a[u]);
    			ans = std::max(ans, sum + a[u]);
    		}
    	} 
    };

    dfs(dfs, 0, -1);

    std::cout << ans << "\n";
}
posted @   maburb  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示