LOJ6534 有趣的题 / UOJ418【集训队作业2018】三角形【贪心】
一道题
给定长为 \(n\) 的正整数序列 \(a_i,b_i\),定义 \(F:2^{[n]}\times\N\rightarrow\N\),其中
求 \(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\)。初始时每个点上都没有石子。可以进行以下两种操作任意多次:
- 当 \(i\) 的所有儿子 \(j\) 分别有至少 \(w_j\) 个石子且手上至少有 \(w_i\) 个石子时,从手中取 \(w_i\) 个石子放在 \(i\) 上。
- 将 \(i\) 上的所有石子拿到手中。
\(\forall i\in[1,n]\),求在 \(i\) 上放至少 \(w_i\) 个石子所需的最少石子数。
\(n\le 2\cdot 10^5,w_i\le 10^9\)。
不知道为什么,考虑将这个过程倒过来做:初始时只有节点 \(i\) 有 \(w_i\) 个石子,每次操作:
- 若 \(i\) 的所有儿子 \(j\) 分别有至少 \(w_j\) 个石子,清空 \(i\) 上的石子。
- 在某个节点上放任意多个石子。
答案即为所有时刻的石子数的最大值。容易知道最优策略中能执行操作 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=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]);
}