省选测试36

省选测试36
T3没删调试,不然有30。后来发现快读写错了,80分没了啊

A Manager

题目大意 : 给出一颗关系树,每人有一个能力值,每人获得的奖金是子树内能力值的中位数(\(\frac{k+1}{2}\)),询问每个人的能力改为1e5之后需要发多少奖金

  • 考场上写的暴力,弄个vector存每个点子树内的所有数,然后sort一下找中位数。一条链就能卡到n2,但数据太水,与是过了(讲完题我就变成95了)

  • 其实还写了线段树合并,只是写的太麻烦,还要区间加打标记,然后没调出来

  • 可以用权值线段树算出中位数,线段树合并,求出每个子树的中位数和中位数下一个数,然后在搜一遍树算答案

  • 算答案就看这个数在祖先里能排到多少,如果在后一半里,变大了也没有,在前一半就变成中位数的下一位了

  • 树状数组可以算出中位数比a[x]打的中位数下一位的和

  • 时间复杂度 \(O(n \log n)\)

Code

Show Code
#include <cstdio>

using namespace std;
typedef long long ll;
const int N = 2e5 + 5;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

struct Edge {
    int n, t;
}e[N];
int h[N], edc;

void Add(int x, int y) {
    e[++edc] = (Edge) {h[x], y}; h[x] = edc;
}

int n, a[N], fa[N], rt[N], trc, m[N], d[N];
ll sum, ans[N];

struct Tree {
    int sz, l, r;
}t[N*21];

void Insert(int &rt, int l, int r, int x) {
    if (!rt) rt = ++trc; t[rt].sz++;
    if (l == r) return;
    int mid = l + r >> 1;
    if (x <= mid) Insert(t[rt].l, l, mid, x);
    else Insert(t[rt].r, mid + 1, r, x);
}

void Marge(int &x, int y) {
    if (!x) return x = y, void();
    t[x].sz += t[y].sz;
    if (t[y].l) Marge(t[x].l, t[y].l);
    if (t[y].r) Marge(t[x].r, t[y].r);
}

int Find(int rt, int l, int r, int x) {
    if (l == r) return l;
    int mid = l + r >> 1;
    if (x <= t[t[rt].l].sz) return Find(t[rt].l, l, mid, x);
    else return Find(t[rt].r, mid + 1, r, x - t[t[rt].l].sz);
}

struct Tarray {
    ll t[N / 2];

    void Add(int x, int w) {
        for (; x; x -= x & -x) t[x] += w;
    }

    ll Ask(int x, int w = 0) {
        for (; x <= 1e5; x += x & -x) w += t[x];
        return w;
    }
}T;

void Dfs(int x) {
    T.Add(m[x], d[x]);
    ans[x] = T.Ask(a[x]) + sum;
    for (int i = h[x]; i; i = e[i].n) Dfs(e[i].t);
    T.Add(m[x], -d[x]);
}

int main() {
    freopen("manager.in", "r", stdin);
    freopen("manager.out", "w", stdout);
    n = read();
    for (int i = 1; i <= n; ++i) a[i] = read();
    for (int i = 2; i <= n; ++i) Add(fa[i] = read(), i);
    for (int i = n; i >= 1; --i) {
        Insert(rt[i], 1, 1e5, a[i]);
        Marge(rt[fa[i]], rt[i]);
        int k = t[rt[i]].sz + 1 >> 1;
        m[i] = Find(rt[i], 1, 1e5, k);
        sum += m[i];
        if (t[rt[i]].sz == 1) d[i] = 1e5 - a[i];
        else d[i] = Find(rt[i], 1, 1e5, k + 1) - m[i];
    }
    Dfs(1);
    for (int i = 1; i <= n; ++i)
        printf("%lld\n", ans[i]);
    return 0;
}

B GCD再放送 (Unaccepted)

题目大意 :

Code

Show Code

C dict

题目大意 : 给出一个不可重集B,和一个排列,让求出比B字典序小的不可重集A的方案数,这里的字典序比较顺序是先比较第p[1]小的,然后比第p[2]小的……

  • 很容易想到枚举A在哪一位第一次比B小,然后前面(p[1]-p[i-1])的位置与B的一样,后面的位置在保证集合性质的大小关系的情况随便选

  • 然后枚举A在这一位是多少,然后枚举A中没选的那些间隙组合数一乘

  • 假如x,y是相邻的确定的位置,那么这中间的方案数就是\(C_{a[y]-a[x]-1}^{y-x-1}\)

  • 这样就是n3的了(假设n与m同阶),发现其实最后一层枚举中除了p[i]前后这两个区间的答案会改变,别的都不变,所以提前算出来就好了,可以优化到n2

  • 这样就有75了,如果在枚举放的是几的时候不枚举显然不合法的,就有80了,考场上如果删了调试,并且把快读写对了就能拿到这分了(话说调试就是因为快读写错了,后来调线段树合并就忘了)

  • 然后发现没放一个p[i]就相当与把1到m这个区间中的一段分成两段,然后扫下面一段,如果每次分的点都是在上面,那么就会跑到n2(相反会O(n)就行)

  • 解决这个的话就可以启发式分裂,因为扫上面得到的答案和扫下面得到的答案加起来就是这一段原本的答案,所以如果上面小的话就可以扫上面,然后那总的减一下就行

  • 时间复杂度 \(O(n \log n)\)

Code

Show Code
#include <set>
#include <cstdio>
#include <algorithm>

using namespace std;
const int N = 2e5 + 5, M = 998244353;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

set<int> s;
int n, m, a[N], p[N], fac[N], inv[N], ans, sum;

int Pow(int a, int k, int ans = 1) {
    for (; k; k >>= 1, a = 1ll * a * a % M)
        if (k & 1) ans = 1ll * ans * a % M;
    return ans;
}

void Init() {
    fac[0] = 1; 
    for (int i = 1; i <= m; ++i)
        fac[i] = 1ll * fac[i-1] * i % M;
    inv[m] = Pow(fac[m], M - 2) % M;
    for (int i = m; i >= 1; --i)
        inv[i-1] = 1ll * inv[i] * i % M;
}

int C(int n, int m) {
    if (n < m) return 0;
    return 1ll * fac[n] * inv[m] % M * inv[n-m] % M;
}

int main() {
    freopen("dict.in", "r", stdin);
    freopen("dict.out", "w", stdout);
    n = read(); m = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    for (int i = 1; i <= n; ++i)
        p[i] = read();
    sort(a + 1, a + n + 1);
    s.insert(0); s.insert(n+1); 
    Init(); a[n+1] = m+1; ans = C(m, n);
    for (int i = 1; i <= n; ++i) {
        set<int>::iterator it = s.lower_bound(p[i]);
        int x, y = p[i], z = *it; x = *--it; s.insert(y);
        int A = y - x - 1, B = z - y - 1, c = C(a[z] - a[x] - 1, z - x - 1);
        ans = 1ll * ans * Pow(c, M - 2) % M;
        if (a[y] - a[x] - 1 <= a[z] - a[y]) {
            for (int j = a[x] + 1; j <= a[y] - 1; ++j)
                if ((sum += 1ll * C(j - a[x] - 1, A) * C(a[z] - j - 1, B) % M * ans % M) >= M) sum -= M;
        }
        else {
            if ((sum += 1ll * ans * c % M) >= M) sum -= M;
            for (int j = a[y]; j <= a[z] - 1; ++j)
                if ((sum -= 1ll * C(j - a[x] - 1, A) * C(a[z] - j - 1, B) % M * ans % M) < 0) sum += M;
        }
        ans = 1ll * C(a[y] - a[x] - 1, y - x - 1) * C(a[z] - a[y] - 1, z - y - 1) % M * ans % M;
    }
    printf("%d\n", sum);
    return 0;
}
上一篇:2021蓝桥杯国赛c++ A组


下一篇:CF1392H - ZS Shuffles Cards