zoukankan      html  css  js  c++  java
  • BJOI 模拟赛 #3 题解

    T1

    一个网格,每个点有权值,求有多少条路径权值乘积不小于 $n$

    $R,C leq 300, n leq 10^6$

    sol:

    暴力 dp 是 $O(R imes C imes n)$ 的

    然后发现如果一条路径大于 $n$ ,直接把它设成 $n$ 即可,然后又发现 $lfloor frac{n}{i} floor$ 只有 $O(sqrt{n})$ 种取值,记录一下即可做到 $O(R imes C imes sqrt{n})$

    #include <bits/stdc++.h>
    #define Debug(x) cerr << #x << " = " << x << '
    '
    #define debug(x) cerr << #x << " = " << x
    #define TAB << " "
    #define EDL << "
    "
    #define LL long long
    #define rep(i, s, t) for(register int i = (s), i##end = (t); i <= i##end; ++i)
    #define dwn(i, s, t) for(register int i = (s), i##end = (t); i >= i##end; --i)
    using namespace std;
    inline int read() {
        int x = 0, f = 1; char ch = getchar();
        for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -f;
        for(; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0';
        return x * f;
    }
    const int mod = 1e9 + 7, maxn = 510;
    int n, m, q, stk[15010], top, bl[1005010];
    int a[maxn][maxn], dp[2][maxn][15010];
    inline void mo(int &x) { if(x >= mod) x -= mod; if(x < 0) x += mod; }
    int main() {
        //freopen("mobitel.in","r",stdin);
        //freopen("mobitel.out","w",stdout);
        n = read(), m = read(), q = read(); q--;
        rep(i, 1, n) rep(j, 1, m) a[i][j] = read();
        for(int l = 1, r; l <= q; l = r + 1) r = q / (q / l), stk[++top] = q / r, bl[q / r] = top;
    //    Debug(top);
        stk[++top] = 0, bl[0] = top;
        int now = q;
        rep(i, 1, m) {
            now /= a[1][i], dp[1][i][bl[now]] = 1;
            //debug(i) TAB; debug(now) TAB; debug(a[1][i]) EDL;
        }
        //Debug(now);
        int cur = 1, last = 0;
        rep(i, 2, n) {
            last = cur, cur = cur ^ 1;
            memset(dp[cur], 0, sizeof(dp[cur]));
            //Debug(cur);
            rep(j, 1, m) {
            //    assert(a[i][j] != 0);
                rep(k, 1, top) {
                    //assert(a[i][j] != 0);
                    dp[cur][j][bl[stk[k] / a[i][j]]] += dp[last][j][k];
                    mo(dp[cur][j][bl[stk[k] / a[i][j]]]);
                    if(j != m) {
                        dp[cur][j + 1][bl[stk[k] / a[i][j + 1]]] += dp[cur][j][k];
                        mo(dp[cur][j + 1][bl[stk[k] / a[i][j + 1]]]);
                    }
                    //debug(i) TAB; debug(j) TAB; debug(k) EDL;
                }
            }
        } cout << dp[cur][m][top] << endl;
    }
    View Code

    T2

    一个有点权和边权的树,一条简单路径合法当且仅当对于这个路径的每一个非空前缀都满足点权大于等于边权,求有多少合法简单路径

    $n leq 100000$

    sol:

    点分治,预处理一下子树里每个点到重心会剩下多少点权,重心到子树里每个点还需要多少点权,在重心处双指针合并一下,容斥即可

    #include <bits/stdc++.h>
    #define LL long long
    #define debug(x) cerr << #x << " = " << x
    #define TAB cerr << " "
    #define EDL cerr << endl
    #define rep(i, s, t) for (register int i = (s), i##end = (t); i <= i##end; ++i)
    #define dwn(i, s, t) for (register int i = (s), i##end = (t); i >= i##end; --i)
    using namespace std;
    inline int read() {
        int x = 0,f = 1; char ch = getchar();
        for(; !isdigit(ch); ch = getchar())if(ch == '-') f = -f;
        for(; isdigit(ch); ch = getchar())x = 10 * x + ch - '0';
        return x * f;
    }
    const int maxn = 100010;
    int n, a[maxn];
    LL ans;
    int first[maxn], to[maxn << 1], nx[maxn << 1], val[maxn << 1], cnt;
    inline void add(int u, int v, int w) {
        to[++cnt] = v;
        nx[cnt] = first[u];
        first[u] = cnt;
        val[cnt] = w;
    }
    int root, sig, ff[maxn], size[maxn], vis[maxn];
    void findroot(int x, int pre) {
        ff[x] = 0; size[x] = 1;
        for(int i=first[x];i;i=nx[i]) {
            if(to[i] == pre || vis[to[i]]) continue;
            findroot(to[i], x); size[x] += size[to[i]];
            ff[x] = max(ff[x], size[to[i]]);
        }
        ff[x] = max(ff[x], sig - size[x]);
        if(ff[x] < ff[root]) root = x;
    }
    LL f[maxn], g[maxn]; int fl, gl;
    void calup(int x, int pre, LL cur, LL mx) {
        if(a[x] >= mx) f[++fl] = (a[x] + cur); mx -= a[x], cur += a[x];
        for(int i=first[x];i;i=nx[i]) {
            if(vis[to[i]] || to[i] == pre) continue;
            calup(to[i], x, cur - val[i], max(mx + val[i], (LL)val[i]));
        }
    }
    void caldw(int x, int pre, LL cur, LL mn) {
        //debug(mn); TAB; debug(cur); EDL;
        g[++gl] = mn; cur += a[x];
        for(int i=first[x];i;i=nx[i]) {
            if(vis[to[i]] || to[i] == pre) continue;
            caldw(to[i], x, cur - val[i], min(mn, cur - val[i]));
        }
    }
    void merge(int opt) {
        int tmp = 0, pos = fl; LL res = 0;
        //cerr << fl << " " << gl << endl;
    //    debug(fl); TAB; debug(gl); EDL;
        sort(f + 1, f + fl + 1); sort(g + 1, g + gl + 1);
        rep(i, 1, gl) {
            while(pos && g[i] + f[pos] >= 0) tmp++, pos--;
            res += tmp;
        } ans += opt * res;
    //    debug(ans);
    }
    void solve(int x) {
        fl = gl = 0;
        vis[x] = 1; caldw(x, 0, 0, 0); calup(x, 0, -a[x], 0); merge(1); ans--;
        for(int i=first[x];i;i=nx[i]) {
            if(vis[to[i]]) continue; fl = gl = 0;
            caldw(to[i], x, a[x] - val[i], a[x] - val[i]);
            calup(to[i], x, -val[i], val[i]); merge(-1);
        }
        for(int i=first[x];i;i=nx[i]) {
            if(vis[to[i]]) continue;
            root = 0; sig = size[to[i]];
            findroot(to[i], x); solve(root); //debug(root);
        }
    }
    int main() {
        //freopen("transport.in","r",stdin);
        //freopen("transport.out","w",stdout);
        n = read();
        rep(i, 1, n) a[i] = read();
        rep(i, 2, n) {
            int u = read(), v = read(), w = read();
            add(u, v, w); add(v, u, w);
        } ff[0] = 2147483233; sig = n;
        findroot(1, 0); solve(root); //debug(root);
        cout << ans << endl;
    }
    View Code

    T3

    有一个游戏,你有 $n$ 个对手,每轮你可以淘汰一些对手,然后拿到 $frac{淘汰人数}{总人数}$ 的钱,现在你只能进行 $k$ 轮,求最多能拿多少钱

    $n,k leq 100000$

    sol:

    容易知道钱数随 $k$ 是单调的,所以可以使用带权二分

    二分一个权值,表示如果多选一轮需要额外花多少钱

    然后用 $f_i$ 表示你已经淘汰了前 $i$ 个对手需要花多少钱,然后看当前轮数是否大于 $k$ ,大于 $k$ 就增加权值,小于 $k$ 就减少权值

    然后就过了

    #include <bits/stdc++.h>
    #define LL long long
    #define DB long double
    #define rep(i, s, t) for (register int i = (s), i##end = (t); i <= i##end; ++i)
    #define dwn(i, s, t) for (register int i = (s), i##end = (t); i >= i##end; --i)
    #define DEBUG
    using namespace std;
    inline int read() {
        int x = 0, f = 1; char ch;
        for (ch = getchar(); !isdigit(ch); ch = getchar()) if (ch == '-') f = -f;
        for (; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0';
        return x * f;
    }
    const int maxn = 100010;
    int n, k, HD, TL, q[maxn], pre[maxn], nx[maxn];
    DB f[maxn];
    //f[i] = max{f[j] - j / (n - j) + i / (n - j)} + mid;
    inline DB Y(int j) { return f[j] - (1.00 * (DB)j) / (1.00 * ((DB)n - (DB)j)); }
    inline DB X(int j) { return 1.00 / (1.00 * ((DB)n - (DB)j)); }
    inline DB slope(int j, int i) { return (Y(i) - Y(j)) / (X(i) - X(j)); }
    inline int check(DB mid, int opt = 0) {
        memset(f, 0, sizeof(f));
        memset(pre, 0, sizeof(pre));
        memset(nx, 0, sizeof(nx));
        memset(q, 0, sizeof(q));
        q[TL = HD = 1] = 0;
        rep(i, 1, n) {
            while(HD < TL && slope(q[HD], q[HD + 1]) >= (DB)(-i)) ++HD;
            int pos = q[HD];
            f[i] = f[pos] + (1.00 * (i - pos)) / (1.00 * (n - pos)) - mid;
            pre[i] = pos;
            while(HD <= TL && slope(q[TL], q[TL - 1]) <= slope(q[TL], i)) --TL;
            q[++TL] = i;
        #ifdef DEBUG
            if(opt)cout << i << " " << pos << endl;
        #endif
        } //system("pause");
        int cur = n, cnt = 0;
        while(cur) {
            cnt++; nx[pre[cur]] = cur;
            cur = pre[cur];
            //cout << cur << endl;
        }
    //    cout << cnt << " " << k << endl;
        //return cnt > k;
        return cnt;
    }
    int main() {
    #ifndef DEBUG
        freopen("quiz.in","r",stdin);
        freopen("quiz.out","w",stdout);
    #endif
        n = read(), k = read();
        DB l = 0, r = 1, mid;
        rep(t, 1, 10000) {
            mid = (l + r) / 2.0; int now;
            if((now = check(mid)) > k) l = mid;
            else if(now < k) r = mid;
            else break;
        } check(mid, 1); double ans = f[n] + k * mid;
        cout << fixed << showpoint << setprecision(9) << ans << endl;
    }
    View Code
  • 相关阅读:
    第二阶段小组站立会议-4-26
    小组站立会议-2014-04-25张永组
    第二阶段小组站立会议-2014-04-24
    小组站立会议-张永组-4-23
    第二阶段会议-阶段目标-张永组
    小组项目第二阶段会议--2014420
    团队下阶段任务分配会议记录-张永组
    Floaty Fish(内测版)发布前一天-------张永组
    电梯调度设计之初感想——蔡迎盈&&曹玉松
    单元测试——我的认识
  • 原文地址:https://www.cnblogs.com/Kong-Ruo/p/10704113.html
Copyright © 2011-2022 走看看