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;
}

3. 2023Xi'anR - Random Variables

tag:容斥;link

\(F(k)\) 表示所有数出现次数 \(\leq k\) 的方案数,那么 \(m^n-F(k)\) 即为存在数出现次数 \(>k\) 的方案数,则 \(n\times m^n-\sum_{k=0}^{n-1}F(k)\) 即为答案,因为一个 \(m^n-F(k)\) 对应着所有众数出现次数 \(>k\) 的方案。根据定义一定有 \(f(0)=0\),所以只用对于每个 \(k\in[1,n)\) 求出 \(F(k)\) 即可。

考虑容斥,设 \(f_{i,j}\) 表示目前填了 \(i\) 个数,有 \(j\) 个数已经被选定次数 \(>k\)。那么转移有:

\[f_{i,j}=(m-j)f_{i-1,j}-(m-j+1)\dbinom{n-i+k}{k}f_{i-k-1,j-1} \]

左边表示在未填的第一个位置中填入一个未被选定的数;右边表示在未填的第一个位置以及后面任意 \(k\) 个位置填一个未被选定的数并且选定它,因为多了一个选定的数所以要乘 \(-1\)

最后答案即为 \(F(k)=\sum f_{n,i}\)。单次复杂度 \(O(\dfrac {n^2}k)\),总复杂度 \(O(n^2\log n)\)

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

const int N = 1e3 + 10;
typedef long long ll;
int T, n;
ll m, P, f[N][N], C[N][N], ans;

struct Mod{
    ll m, p;
    void init(int pp){
        m = ((__int128)1 << 64) / pp;
        p = pp;
    }
    ll operator ()(ll x){
        if(p == 2){
            return x & 1;
        }
        return x - ((__int128(x) * m) >> 64) * p;
    }
} mod;

ll qp(ll x, ll y){
    ll ans = 1;
    while(y){
        if(y & 1){
            ans = mod(ans * x);
        }
        x = mod(x * x);
        y >>= 1;
    }
    return ans;
}

int main(){
    scanf("%d%lld", &T, &P);
    n = 1000;
    mod.init(P);
    C[0][0] = 1;
    for(int i = 1; i <= n; ++ i){
        C[i][0] = 1;
        for(int j = 1; j <= i; ++ j){
            C[i][j] = C[i-1][j] + C[i-1][j-1];
            if(C[i][j] >= P){
                C[i][j] -= P;
            }
        }
    }
    while(T--){
        ans = 0;
        scanf("%d%lld", &n, &m);
        for(int k = 1; k < n; ++ k){
            f[0][0] = 1;
            for(int i = 1; i <= n; ++ i){
                f[i][i/(k+1)+1] = 0;
                for(int j = 0; j * (k + 1) <= i; ++ j){
                    f[i][j] = mod(f[i-1][j] * (m - j));
                    if(i >= k + 1 && j){
                        ll tmp = mod(f[i-k-1][j-1] * (m - j + 1));
                        tmp = mod(tmp * C[n-(i-k)][k]);
                        f[i][j] -= tmp;
                        if(f[i][j] < 0){
                            f[i][j] += P;
                        }
                    }
                }
            }
            for(int j = 0; j * (k + 1) <= n; ++ j){
                ans += f[n][j];
                f[n][j] = 0;
                if(ans >= P){
                    ans -= P;
                }
            }
        }
        printf("%lld\n", mod(- ans + P + n * qp(m, n)) % P);
    }
    return 0;
}

4. APIO2024 - Train

考虑按时间顺序 dp,设 \(f_{x,i}\) 表示 \(i\) 时刻在点 \(x\) 的最小费用,则对于每条星琼铁道有:

\[f_{Y,B}=\min_{i\leq A}\{f_{X,i}+T*w(i,A)\}+C \]

其中 \(w(a,b)\) 表示 \([L,R]\in (a,b)\) 的吃饭时间数,可以使用主席数求出单个的 \(w\)

容易发现有 \(w(a,b+1)+w(a+1,b)\geq w(a,b)+w(a+1,b+1)\),即相交优于包含,可以在每个点开一个队列表示决策,二分队列决策单调性求解。

二分队列算法步骤:

  • 计算队首两个决策 \(l,l+1\) 的交点是否 \(\leq i\),如果是则弹出队首;
  • 若存在队首,用队首更新此时的答案;
  • 计算队尾两个决策 \(r-1,r\) 的交点是否 \(\geq\) 队尾决策 \(r\) 与当前决策的交点,如果是则弹出队尾;
  • 插入当前决策。
  • 可以使用第二个队列维护队内交点减少算法常数。
  • 使用二分求解两个决策交点,具体的,求出第一个下标使得前者劣于后者。
点击查看代码
//qoj8725
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
#ifndef ONLINE_JUDGE
#include "train.h"
#endif
const int N = 1e5 + 10;
int n, m, w, pl[N*4], tp;
basic_string<int> eat[N*4], que[N], qcr[N];
basic_string<pair<int, int> > tra[N*4];
int lp[N], rp[N], mc[N];
unordered_map<int, ll> f[N];
unordered_map<int, int> vs[N];

struct SegTree{
    int rt[N*4], tot = 1;
    struct node{
        int sum, ls, rs;
    } t[N*100];
    void add(int &p, int l, int r, int x){
        ++tot;
        t[tot] = t[p];
        p = tot;
        if(l == r){
            ++ t[p].sum;
        } else {
            int mid = l + r >> 1;
            if(x <= mid){
                add(t[p].ls, l, mid, x);
            } else {
                add(t[p].rs, mid+1, r, x);
            }
            t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
        }
    }
    int ask(int p, int l, int r, int ql, int qr){
        if(qr < l || r < ql || !p){
            return 0;
        } else if(ql <= l && r <= qr){
            return t[p].sum;
        } else {
            int mid = l + r >> 1;
            return ask(t[p].ls, l, mid, ql, qr) + 
                   ask(t[p].rs, mid+1, r, ql, qr);
        }
    }
    ll ask(int l, int r){
        return ask(rt[r-1], 1, tp, l+1, tp);
    }
} T;

ll fx(int x, int p, int val){
    return f[x][p] + mc[x] * T.ask(p, val);
}
int crs(int x, int p, int q){
    int l = q, r = tp + 1;
    while(l < r){
        int mid = l + r >> 1;
        if(fx(x, p, mid) < fx(x, q, mid)){
            l = mid + 1;
        } else {
            r = mid;
        }
    }
    return l;
}

ll solve(int N, int M, int W, vint mcc, 
         vint xx, vint yy, vint st, vint ed, vint lc,
         vint le, vint ri){
    n = N;
    m = M;
    w = W;
    for(int i = 0; i < n; ++ i){
        mc[i] = mcc[i];
        lp[i] = 1;
        rp[i] = 0;
        que[i].push_back(0);
        qcr[i].push_back(0);
    }
    for(int i : st){
        pl[++tp] = i;
    }
    for(int i : ed){
        pl[++tp] = i;
    }
    for(int i : le){
        pl[++tp] = i;
    }
    for(int i : ri){
        pl[++tp] = i;
    }
    sort(pl + 1, pl + tp + 1);
    tp = unique(pl + 1, pl + tp + 1) - pl - 1;
    for(int i = 0; i < m; ++ i){
        st[i] = lower_bound(pl + 1, pl + tp + 1, st[i]) - pl;
        ed[i] = lower_bound(pl + 1, pl + tp + 1, ed[i]) - pl;
        tra[st[i]].push_back(make_pair(-i-1, xx[i]));
        tra[ed[i]].push_back(make_pair(i+1, yy[i]));
    }
    for(int i = 0; i < w; ++ i){
        le[i] = lower_bound(pl + 1, pl + tp + 1, le[i]) - pl;
        ri[i] = lower_bound(pl + 1, pl + tp + 1, ri[i]) - pl;
        eat[ri[i]].push_back(le[i]);
    }
    for(int i = 1; i <= tp; ++ i){
        T.rt[i] = T.rt[i-1];
        for(int j : eat[i]){
            T.add(T.rt[i], 1, tp, j);
        }
    }
    f[0][0] = 0;
    que[0].push_back(0);
    qcr[0].push_back(0);
    que[0][++rp[0]] = 0;
    for(int i = 1; i <= tp; ++ i){
        sort(tra[i].begin(), tra[i].end());
        for(auto j : tra[i]){
            int id = j.first, pos = j.second;
            if(id < 0){
                id = -id - 1;
                ll val = 1e18;
                while(lp[pos] < rp[pos] && qcr[pos][lp[pos]] <= i){
                    ++ lp[pos];
                }
                if(lp[pos] > rp[pos]){
                    continue;
                }
                int p = que[pos][lp[pos]];
                val = f[pos][p] + mc[pos] * T.ask(p, i);
                if(f[pos].find(i) != f[pos].end()){
                    f[pos][i] = min(f[pos][i], val);
                } else {
                    f[pos][i] = val;
                }
            } else {
                -- id;
                if(f[xx[id]].find(st[id]) == f[xx[id]].end()){
                    continue;
                }
                ll val = f[xx[id]][st[id]] + lc[id];
                if(f[pos].find(i) != f[pos].end()){
                    f[pos][i] = min(f[pos][i], val);
                } else {
                    f[pos][i] = val;
                }
                while(lp[pos] < rp[pos] &&
                      qcr[pos][rp[pos]-1] >=
                      crs(pos, que[pos][rp[pos]], i)){
                    -- rp[pos];
                }
                que[pos].push_back(0);
                qcr[pos].push_back(0);
                que[pos][++rp[pos]] = i;
                if(lp[pos] < rp[pos]){
                    qcr[pos][rp[pos]-1] = crs(pos, que[pos][rp[pos]-1], que[pos][rp[pos]]);
                }
            }
        }
    }
    ll val = 1e18;
    for(auto i : f[n-1]){
        val = min(val, i.second + 1ll * mc[n-1] * T.ask(i.first, tp+1));
    }
    if(val > 1e17){
        return -1;
    }
    return val;
}
posted @ 2024-10-07 19:25  KiharaTouma  阅读(35)  评论(0)    收藏  举报