LOJ6534 有趣的题 / UOJ418【集训队作业2018】三角形【贪心】

一道题

给定长为 (n) 的正整数序列 (a_i,b_i),定义 (F:2^{[n]} imesN ightarrowN),其中

[F(S,t)=egin{cases}min{a_i+F(Sackslash{i},b_i+max(t-a_i,0))mid iin S} & S evarnothing \ t & S=varnothingend{cases} ]

(F([n],0))(T) 组数据。

(Tle 5,nle2cdot 10^6,a_i,b_ile 10^7)


手玩一下,假设先确定了选数顺序,设为 (n,n-1,cdots,1),可以得到 (F([n],t)) 即为 ( exttt{for }i:1 ightarrow n),将 (t)(a_1+a_2+cdots+a_i)(max),然后加上 (b_i)。问题就在于要确定 ((a_i,b_i)) 的排列使得最终的 (t) 最小。

考虑造一个组合意义(?),搞一个 (2 imes n) 的网格图,第 (1) 行第 (i) 列的权值为 (a_i),第 (2) 行第 (i) 列的权值为 (b_i),最终的 (t) 即为从 ((1,1))((2,n)) 的所有格路的权值之和的最大值。要求这个值最小。

这好像是经典贪心题来着(?

结论 1:(a_i>b_i)(a_{i+1}le b_{i+1}),则交换 (i,i+1) 之后答案不劣。

证明:设 (w_i) 表示在第 (i) 列向下走的方案的权值之和,(w_i') 为交换后的值,则有 (w_i'<w_{i+1})(w_{i+1}'le w_i)

结论 2:若 (a_ile b_i)(a_{i+1}le b_{i+1})(a_i>a_{i+1}),则交换 (i,i+1) 之后答案不劣。

证明:(w_i'<w_i)(w'_{i+1}<w_{i+1})

结论 3:若 (a_i>b_i)(a_{i+1}>b_{i+1})(b_i<b_{i+1}),则交换 (i,i+1) 之后答案不劣。

证明:同结论 2。

于是就得到了贪心方法:将 (a_ile b_i) 的按 (a_i) 升序排序放前面,(a_i>b_i) 的按 (b_i) 降序排序放后面。时间复杂度 (O( exttt{Sort}(n)))

#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 2000003;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
} template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int T, n, c, d, a[N], b[N]; pii p[N], q[N];
void init(int *a){
	int y, z; LL x; read(a[1]); read(x); read(y); read(z);
	for(int i = 2;i <= n;++ i) a[i] = (a[i-1] * x + y) % z + 1;
} void solve(){
	read(n); init(a); init(b); c = d = 0;
	for(int i = 1;i <= n;++ i)
		if(a[i] <= b[i]) p[++c] = MP(a[i], b[i]);
		else q[++d] = MP(-b[i], -a[i]);
	sort(p + 1, p + c + 1); sort(q + 1, q + d + 1);
	LL cur = 0, ans = 0;
	for(int i = 1;i <= c;++ i){chmax(ans, cur += p[i].fi); ans += p[i].se;}
	for(int i = 1;i <= d;++ i){chmax(ans, cur -= q[i].se); ans -= q[i].fi;}
	printf("%lld
", ans);
} int main(){read(T); while(T --) solve();}

另一道题

给定 (n) 个点的以 (1) 为根的树,第 (i) 个点有权值 (w_i)。初始时每个点上都没有石子。可以进行以下两种操作任意多次:

  1. (i) 的所有儿子 (j) 分别有至少 (w_j) 个石子且手上至少有 (w_i) 个石子时,从手中取 (w_i) 个石子放在 (i) 上。
  2. (i) 上的所有石子拿到手中。

(forall iin[1,n]),求在 (i) 上放至少 (w_i) 个石子所需的最少石子数。

(nle 2cdot 10^5,w_ile 10^9)


不知道为什么,考虑将这个过程倒过来做:初始时只有节点 (i)(w_i) 个石子,每次操作:

  1. (i) 的所有儿子 (j) 分别有至少 (w_j) 个石子,清空 (i) 上的石子。
  2. 在某个节点上放任意多个石子。

答案即为所有时刻的石子数的最大值。容易知道最优策略中能执行操作 1(清空)就一定会执行,所以可以看做:

  • 先放入 (a_i=sum_{(i,j)in E}w_j) 个石子,再取走 (b_i=w_i) 个石子。

则答案为 (max{a_1,a_1+a_2-b_1,cdots,a_1+cdots+a_n-b_1-cdots-b_{n-1}})。设 (f_i) 表示前 (i) 项的最大值,则有

[f_i+b_1+cdots+b_i=max{f_{i-1}+b_1+cdots+b_{i-1},a_1+cdots+a_i}+b_i ]

(F_i=f_i+b_1+cdots+b_i),则有 (F_i=max{F_{i-1},a_1+cdots+a_i}+b_i)于是我们得到了经典打怪兽模型跟上面这个题是一样的

于是可以套用贪心结论,用 ((a_i-b_i,a_i)) 表示一次操作,两次操作 ((Delta x_1,p_1)+(Delta x_2,p_2)=(Delta x_1+Delta x_2,max{p_1,Delta x_1+p_2})),且 ((Delta x_1,p_1)le (Delta x_2,p_2)) 当且仅当

  • (Delta x_1le 0)(Delta x_2>0)
  • (Delta x_1,Delta x_2le 0)(p_1le p_2)
  • (Delta x_1,Delta x_2>0)(p_1-Delta x_1ge p_2-Delta x_2)

当然实际排序的时候还要带上编号作为第三关键字,这样所有操作就形成了完美的全序关系

然而树上限制比较麻烦,要求操作序列是外向拓扑序。

考虑一个做法:每次用 priority_queue/set 维护当前可用操作中最优的,找到最优操作并执行它。

然后发现这个过程实际上就类似于做 Prim,于是我们做 Kruskal 也是可以的!(?

在一开始将除了根节点之外的所有操作加进来并排序,每次找到最优操作 (i),将 (i) 与其父亲合并,把 (i) 的操作序列接到父亲后面。由于需要合并所以还是要搞 set 动态维护全序集(

排完序之后,我们知道子树内部的排序结果一定是整树的排序结果的子序列,所以只要以每个元素在排序结果中的 rnk 为下标建立线段树,求答案就可以直接用线段树合并。

时间复杂度 (O(nlog n))

#include<bits/stdc++.h>
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;
const int N = 200003, M = N<<5, mod = 1e9 + 7;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
} int n, p[N], fa[N], rt[N], ls[M], rs[M], tot, tim; LL f[N], g[N], a[N], b[N], ans[N];
int getfa(int x){return x == fa[x] ? x : fa[x] = getfa(fa[x]);}
struct Node {
	LL a, b;
	Node(LL _a = 0, LL _b = -1e18): a(_a), b(_b){}
	Node operator + (const Node &o) const {return Node(a + o.a, max(b, a + o.b));}
} seg[M]; set<pii> S; vector<int> E[N];
void upd(int &x, int L, int R, int p, Node tmp){
	if(!x) x = ++tot;
	if(L == R){seg[x] = tmp; return;}
	int mid = L+R>>1;
	if(p <= mid) upd(ls[x], L, mid, p, tmp);
	else upd(rs[x], mid+1, R, p, tmp);
	seg[x] = seg[ls[x]] + seg[rs[x]];
} void merge(int &x, int y){
	if(!x || !y){x += y; return;}
	merge(ls[x], ls[y]); merge(rs[x], rs[y]);
	seg[x] = seg[ls[x]] + seg[rs[x]];
} void dfs(int x){
	upd(rt[x], 1, n, ++tim, Node(g[x], f[x]));
	for(int v : E[x]) dfs(v);
} int main(){
	read(n); read(n);
	for(int i = 2;i <= n;++ i) read(p[i]);
	for(int i = 1;i <= n;++ i){
		read(b[i]); a[p[i]] += b[i]; fa[i] = i;
	} for(int i = 1;i <= n;++ i){b[i] = a[i] - b[i]; f[i] = a[i]; g[i] = b[i];}
	for(int i = 2;i <= n;++ i) if(b[i] < 0) S.insert(MP(a[i], i));
	while(!S.empty()){
		int u = S.begin()->se, fu = getfa(p[u]); S.erase(S.begin());
		if(fu > 1 && b[fu] < 0) S.erase(S.find(MP(a[fu], fu)));
		a[fu] = max(a[fu], b[fu] + a[u]); b[fu] += b[u];
		if(fu > 1 && b[fu] < 0) S.insert(MP(a[fu], fu));
		E[fu].PB(u); fa[u] = fu;
	} dfs(1);
	for(int i = n;i;-- i){ans[i] = f[i] - g[i] + seg[rt[i]].b; merge(rt[p[i]], rt[i]);} 
	for(int i = 1;i <= n;++ i) printf("%lld ", ans[i]);
}
原文地址:https://www.cnblogs.com/AThousandMoons/p/14582478.html