zoukankan      html  css  js  c++  java
  • 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]);
    }
    
  • 相关阅读:
    十问5.12汶川大地震
    JZ035数组中的逆序对
    JZ037数字在排序数组中出现的次数
    JZ039平衡二叉树
    JZ033丑数
    JZ040数组中只出现一次的数字
    JZ032把数组排成最小的数
    JZ036两个链表的第一个公共结点
    JZ034第一个只出现一次的字符位置
    JZ031从 1 到 n 整数中 1 出现的次数
  • 原文地址:https://www.cnblogs.com/AThousandMoons/p/14582478.html
Copyright © 2011-2022 走看看