stream pack 1.

收录动态规划题

1. LuoguP11189 -「KDOI-10」水杯降温

tag:二分、状态设计;link

发现只用操作 \(1\) 就能够完成当且仅当:

  • \(\forall i,a_i\leq 0\)
  • \(a_i-\sum_{j\in son_i}a_j=0\)

\(b_i=a_i-\sum_{j\in son_i}a_j\),考虑操作 \(2\) 对于 \(b\) 数组的影响。设 \(c_i\) 表示点 \(i\) 上操作 \(2\) 的次数,\(b,c\) 的关系并不简单,但是若设 \(f_i\) 表示 \(i\to 1\)\(c\) 的和,那么有简单的式子:

\[b_i=(\sum_{j\in son_i}f_j)-f_i \]

猜测 \(f_i\) 的取值是一个区间,其实真是。

对于叶子,\(f_i\in[\max(0, -b_i),+\inf)\);对于非叶子,有

\[b_i+f_i=\sum f_j,f_i\leq f_j \]

计算左端点后即可二分查找右端点。

点击查看代码
//P11189
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
typedef long long ll;
const ll inf = 5e18;
int c, T, n, lf[N];
vector<int> g[N];
ll a[N], b[N], le[N], ri[N];

bool chk(int x, ll v, int fa){
    ll mn = 0, mx = 0;
    for(int i : g[x]){
        if(i == fa){
            continue;
        }
        if(ri[i] < v){
            return 0;
        }
        mn += max(le[i], v);
        mx += ri[i];
        mx = min(mx, inf);
    }
    ll val = b[x] + v;
    return val >= mn && val <= mx;
}

void dfs(int x, int fa){
    if(lf[x]){
        le[x] = max(0ll, -b[x]);
        ri[x] = 1e18;
        return;
    }
    for(int i : g[x]){
        if(i != fa){
            dfs(i, x);
            if(le[i] > ri[i]){
                le[x] = 1;
                ri[x] = 0;
                return;
            }
        }
    }
    ll sum = 0;
    for(int i : g[x]){
        if(i != fa){
            sum += le[i];
        }
    }
    le[x] = max(0ll, sum - b[x]);
    ll L = le[x]-1, R = 1.1e13;
    while(L < R){
        ll mid = L + R + 1 >> 1;
        if(chk(x, mid, fa)){
            L = mid;
        } else {
            R = mid - 1;
        }
    }
    ri[x] = L;
}

int main(){
    scanf("%d%d", &c, &T);
    while(T--){
        scanf("%d", &n);
        for(int i = 1; i <= n; ++ i){
            lf[i] = 1;
            vector<int> ().swap(g[i]);
        }
        for(int i = 2; i <= n; ++ i){
            int fa;
            scanf("%d", &fa);
            lf[fa] = 0;
            g[fa].push_back(i);
        }
        for(int i = 1; i <= n; ++ i){
            scanf("%lld", &a[i]);
        }
        for(int i = 1; i <= n; ++ i){
            b[i] = a[i];
            for(int j : g[i]){
                b[i] -= a[j];
            }
        }
        dfs(1, 0);
        if(le[1] <= ri[1]){
            puts("Huoyu");
        } else {
            puts("Shuiniao");
        }
    }
    return 0;
}

2. qoj365/JOISC2017 - 鉄道旅行 (Railway Trip)

tag:倍增、正确性证明;link

容易发现一个结论:假设一条路径 \(A=x_1,x_2,...,x_p=B\),那么一定有一条最短路满足存在 \(q\) 使得 \(\forall i\in[1,q),L_{x_i}\leq L_{x_{i+1}};\forall i\in[q,p),L_{x_i}\geq L_{x_{i+1}}\),即经过的所有点的 \(L\) 是非严格单峰的。

所以重新建图:对点 \(i\),找到它两侧离它最近的两个点 \(p,q\) 满足 \(L_p\geq L_i\leq L_q\) 并连边 \(L_i\to L_p,L_i\to L_q\)。那么原图的一条最短路可以转化为新图的 \(A\to C,B\to C\) 的两条路径。

倍增,设 \(l/r_{i,j}\) 表示 \(i\) 开始跳 \(2^j\) 步两侧到哪些点,容易证明这也意味着能到 \(L\) 最大的哪两个点。

所以询问时从 \(A\) 开始逼近 \(B\) 变成 \(A'\),然后 \(B\) 逼近 \(A'\) 得到答案,相当于先跑单峰左边的递增,再跑右边的,所以是正确的。

点击查看代码
//qoj365
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int n, k, q, a[N], le[N][20], ri[N][20];
int st[N], tp;

int main(){
    scanf("%d%d%d", &n, &k, &q);
    st[tp=0] = 1;
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &a[i]);
        while(tp && a[st[tp]] < a[i]) -- tp;
        le[i][0] = st[tp];
        st[++tp] = i;
    }
    st[tp=0] = n;
    for(int i = n; i >= 1; -- i){
        while(tp && a[st[tp]] < a[i]) -- tp;
        ri[i][0] = st[tp];
        st[++tp] = i;
    }
    for(int i = 1; i <= 19; ++ i){
        for(int j = 1; j <= n; ++ j){
            le[j][i] = min(le[le[j][i-1]][i-1], le[ri[j][i-1]][i-1]);
            ri[j][i] = max(ri[ri[j][i-1]][i-1], ri[le[j][i-1]][i-1]);
        }
    }
    while(q--){
        int x, y, ans = 0;
        scanf("%d%d", &x, &y);
        if(x > y) swap(x, y);
        int p = x, q = x;
        for(int i = 19; i >= 0; -- i){
            int pp = min(le[p][i], le[q][i]);
            int qq = max(ri[p][i], ri[q][i]);
            if(qq < y){
                p = pp;
                q = qq;
                ans += 1 << i;
            }
        }
        x = q;
        p = y, q = y;
        for(int i = 19; i >= 0; -- i){
            int pp = min(le[p][i], le[q][i]);
            int qq = max(ri[p][i], ri[q][i]);
            if(pp > x){
                p = pp;
                q = qq;
                ans += 1 << i;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
posted @ 2024-10-07 19:25  KiharaTouma  阅读(5)  评论(0编辑  收藏  举报