BJOI 模拟赛 #3 题解

T1

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

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

sol:

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

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

#include <bits/stdc++.h>
#define Debug(x) cerr << #x << " = " << x << '\n'
#define debug(x) cerr << #x << " = " << x
#define TAB << " "
#define EDL << "\n"
#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

 

posted @ 2019-04-14 10:20  探险家Mr.H  阅读(300)  评论(0编辑  收藏  举报