[每日随题5] 贪心 - 树型DP - 邓老师调整法

整体概述

  • 难度:900 -> 2000 -> 3000

1468N.Waste Sorting

  • 标签:贪心

  • 前置知识:无

  • 难度:ICPC.N 900

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

7
1 2 3
1 2 3 0 0
2 2 3
1 2 3 1 0
2 2 3
1 2 3 0 1
1 2 5
1 2 3 1 1
0 0 0
0 0 0 0 0
0 0 4
1 0 0 0 0
13 37 42
0 0 0 40 47

样例输出:

YES
YES
NO
YES
YES
NO
YES

解题思路:

  • 对于前三类物品,直接丢入对应的垃圾桶内。

  • 对于第四、五两类物品,优先丢到前两种垃圾桶内,前两种不够丢的时候丢到第三个垃圾桶中。

  • 当某个垃圾桶不够丢时,则输出 NO,否则输出 YES

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int N = 5e5+5;
inline string solve(){
	int c[4],a[6];
	for(int i=1;i<=3;i++) cin >> c[i];
	for(int i=1;i<=5;i++) cin >> a[i];
	for(int i=1;i<=3;i++) c[i] -= a[i];
	if(c[1]>0){
		int x = min(c[1],a[4]);
		c[1] -= x, a[4] -= x;
	}
	if(c[2]>0){
		int x = min(c[2],a[5]);
		c[2] -= x, a[5] -= x;
	}
	c[3] -= a[4] + a[5];
	for(int i=1;i<=3;i++) if(c[i]<0) return "NO";
	return "YES";
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) cout << solve() << '\n';
	return 0;
}

1363E.Tree Shuffling

  • 标签:树型DP

  • 前置知识:链式前向星

  • 难度:Div.2.E 2000

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

5
1 0 1
20 1 0
300 0 1
4000 0 0
50000 1 0
1 2
2 3
2 4
1 5
5
10000 0 1
2000 1 0
300 0 1
40 0 0
1 1 0
1 2
2 3
2 4
1 5
2
109 0 1
205 0 1
1 2

样例输出:

4
24000
-1

解题思路:

  • 由于操作可以选择任意一个节点 \(u\) 将其所有子节点进行操作,显然如果父节点的价格比子节点更低,直接在父节点上进行所有该子节点的操作是等价的。

    那么我们进行一遍 \(DFS\) 直接将每个节点的价格重置为其所有父节点中最便宜的价格,所有在该子节点上的操作直接使用该子节点重置后的价格即可。

  • 随后考虑如何计算匹配所有节点的最小价格。我们节点需要从 \(0->1\) 为状态 \(1\),需要从 \(1->0\) 为状态 \(2\),则我们只需要将所有状态 \(1\) 和状态 \(2\) 的节点一一其数值即可。

  • 那么我们从叶节点往上,每个节点记录自己的子树上有多少个状态 \(1\) 和状态 \(2\) 的节点,处理完后返回所用的价格,两种状态的节点的剩余数量给自己的父节点,让其处理剩余的部分。最后如果根节点处的价格即处理完所有节点的总价格,

  • 如果根节点仍然存在状态 \(1\) 或者状态 \(2\) 的节点,则输出 \(-1\)。否则直接输出根节点的总价即可。

  • 总复杂度 \(O(n)\)

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int N = 2e5+5;
int n,val[N],ha[N],idx;
bool b[N],c[N];
struct Edge{int to,ne;}edge[N<<1];
inline void ins(int u,int v){
	edge[++idx] = {v,ha[u]}, ha[u] = idx;
}
struct Info{
	int cost,cnt1,cnt2;	// 1:1->0   2:0->1
	Info(int cost=0,int cnt1=0,int cnt2=0):cost(cost),cnt1(cnt1),cnt2(cnt2){}
	Info operator + (const Info &x){
		return Info(cost+x.cost,cnt1+x.cnt1,cnt2+x.cnt2);
	} 
};
inline void dfs1(int u,int par){
	if(u != 1) val[u] = min(val[u],val[par]);
	for(int i=ha[u];i;i=edge[i].ne){
		int v = edge[i].to;
		if(v != par) dfs1(v,u);
	}
}
inline Info dfs2(int u,int par){
	Info res;
	if(b[u] && !c[u]) res.cnt1 += 1;
	if(!b[u] && c[u]) res.cnt2 += 1;
	for(int i=ha[u];i;i=edge[i].ne){
		int v = edge[i].to;
		if(v == par) continue;
		res = res + dfs2(v,u);
	}
	int cnt = min(res.cnt1,res.cnt2);
	res.cost += 2*cnt*val[u];
	res.cnt1 -= cnt, res.cnt2 -= cnt;
	return res;
}
inline void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> val[i] >> b[i] >> c[i];
	for(int i=1,u,v;i<n;i++){
		cin >> u >> v;
		ins(u,v),ins(v,u);
	}
	dfs1(1,0);
	Info res = dfs2(1,0);
	if(res.cnt1 || res.cnt2) cout << -1;
	else cout << res.cost;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; T = 1;
	while(T--) solve();
	return 0;
}

280E.Sequence Transformation

  • 标签:邓老师调整法

  • 前置知识:差分数组

  • 难度:Div.1.E 3000

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

3 6 2 2
1 4 6
10 100000 8714 9344
3378 14705 17588 22672 32405 34309 37446 51327 81228 94982

样例输出:

1.666667 3.666667 5.666667 
0.666667
1.000000 8715.000000 17429.000000 26143.000000 34857.000000 43571.000000 52285.000000 61629.000000 70973.000000 80317.000000 
797708674.000000

解题思路:

  • 我们的目标是构造出一个满足条件的序列,使得代价尽可能小。可以尝试先构造出一个满足条件的解,然后将其慢慢调整至最优解。

  • 我们设数组 \(y\) 的差分为数组 \(d\),那么我们可以构造出 \(y_1 = 1\)\(d_i = a\) 的一个合法序列,然后慢慢尝试调大每个 \(d_i\),达到最小的答案。

  • 那么我们需要考虑,将某个位置的 \(d_k\) 增大 \(v\) 会带来的影响。原先每个位置 \(i\) 的代价为 \((y_i-x_i)^2\),则使一个位置 \(d_k\) 增大 \(v\) 带来的影响为 \(\sum_{i=k}^{n}(y_i-x_i+v)^2-(y_i-x_i)^2\) = \((n-k+1)*v^2+2*v*\sum_{i=k}^{n}(y_i-x_i)\)

  • 对于相同的增加量 \(v\),想要使答案减小的最多,那么需要 \((n-k+1)\) 尽可能小,\(\sum_{i=k}^{n}(y_i-x_i)\) 为负数且尽可能小。所以我们每次找到 \(\sum_{i=k}^{n}(y_i-x_i)\) 最小且为负数的位置 \(k\),将其增加到不是最小值或者到达限制。

  • 最后统计出所有的 \(y_i\),再统计出所有的代价即可。

完整代码

#include <bits/stdc++.h>
#pragma optimize(3)
#define lb long double
using namespace std;
const int N = 2e5+5; const lb eps = 1e-5;
int n,q,a,b,x[N];
lb d[N],y[N],s[N];
signed main() {
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cout << fixed << setprecision(6);
	cin >> n >> q >> a >> b;
	for(int i=1;i<=n;i++) cin >> x[i];
	d[1] = 1;
	for(int i=2;i<=n;i++) d[i] = a;
	lb rest = q-1 - a*(n-1);	// 最大 q 的限制
	while(rest){ 
		y[1] = d[1];
		for(int i=2;i<=n;i++) y[i] = y[i-1] + d[i];
		for(int i=n;i>=1;--i) s[i] = s[i+1] + y[i]-x[i];
		int p = 1;
		for(int i=2;i<=n;i++) if(s[i]<=s[p]+eps && d[i]+eps<=b) p = i;
		if(s[p] >= 0) break;
		lb v = min(-s[p]/(n-p+1),rest);	// s[p]<0的限制 与 rest的限制
		if(p != 1) v = min(v,b-d[p]);	// 上界b的限制
		for(int i=p+1;i<=n;i++) if(d[i]+eps<=b) v = min((s[i]-s[p])/(i-p),v);	// 最多增加到非最小 s[p]
		d[p] += v, rest -= v;
	} 
	y[1] = d[1];
	for(int i=2;i<=n;i++) y[i] = y[i-1]+d[i];
	lb ans = 0;
	for(int i=1;i<=n;i++){ 
		cout << y[i] << ' ';
		ans += (y[i]-x[i])*(y[i]-x[i]);
	} 
	cout << '\n' << ans <<'\n';
	return 0;
}

posted @ 2025-07-12 00:36  浅叶梦缘  阅读(17)  评论(0)    收藏  举报