一道题
给定长为 (n) 的正整数序列 (a_i,b_i),定义 (F:2^{[n]} imesN ightarrowN),其中
求 (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)。初始时每个点上都没有石子。可以进行以下两种操作任意多次:
- 当 (i) 的所有儿子 (j) 分别有至少 (w_j) 个石子且手上至少有 (w_i) 个石子时,从手中取 (w_i) 个石子放在 (i) 上。
- 将 (i) 上的所有石子拿到手中。
(forall iin[1,n]),求在 (i) 上放至少 (w_i) 个石子所需的最少石子数。
(nle 2cdot 10^5,w_ile 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_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]);
}