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

一道题

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

\[F(S,t)=\begin{cases}\min\{a_i+F(S\backslash\{i\},b_i+\max(t-a_i,0))\mid i\in S\} & S\ne\varnothing \\ t & S=\varnothing\end{cases} \]

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

\(T\le 5,n\le2\cdot 10^6,a_i,b_i\le 10^7\)


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

考虑造一个组合意义(?),搞一个 \(2\times 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_i\le 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_i\le b_i\) 的按 \(a_i\) 升序排序放前面,\(a_i>b_i\) 的按 \(b_i\) 降序排序放后面。时间复杂度 \(O(\texttt{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\n", 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 i\in[1,n]\),求在 \(i\) 上放至少 \(w_i\) 个石子所需的最少石子数。

\(n\le 2\cdot 10^5,w_i\le 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_1\le 0\)\(\Delta x_2>0\)
  • \(\Delta x_1,\Delta x_2\le 0\)\(p_1\le p_2\)
  • \(\Delta x_1,\Delta x_2>0\)\(p_1-\Delta x_1\ge p_2-\Delta x_2\)

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

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

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

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

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

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

时间复杂度 \(O(n\log 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]);
}
posted @ 2021-03-26 15:22  mizu164  阅读(146)  评论(0编辑  收藏  举报