2023冲刺国赛模拟 2.1

T1 树

首先考虑初始节点只有 \(1\) 个的情况,很容易使用 dp 解决,设 \(f_i\) 表示初始节点为 \(i\) ,占领以 \(i\) 为根的子树所需要的最小回合数量,只需要优先占领回合多的子树即可。

当初始节点为 \(2\) 个时,容易发现 \(u,v\) 路径上存在一条边,满足最优方案下 \(u,v\) 的士兵均不会跨过这条边,因此可以枚举这条边并对边两侧的子树分别 dp ,容易发现 \(u,v\) 两侧的 dp 值随着边位置的单调移动具有单调性,因此可以二分两侧 dp 值最接近的位置,这个位置取到最优方案。

直接做复杂度为 \(O(n\log^2 n)\) ,考虑进行优化,容易发现每次进行 dp 进行了许多重复的计算,因此考虑断掉链上所有边预处理每个地方的 dp 值,二分 check 时只需要连接链即可,此时我们相当于在原先节点下连接一棵子树,设当前连接的子树的贡献为 \(res\) ,此时我们需要将 \(res\) 插入到对应节点的子树序列中,我们设原先这个节点 \(i\) 取到最大 dp 值的位置为 \(pos\) ,如果 \(res\) 可以插入到 \(pos\) 之前,显然会产生 \(f_i+1\) 的贡献,否则产生 \(f_i\) 的贡献,同时考虑到 \(res\) 插入到序列开头贡献为 \(res+1\) ,对这几种贡献取 \(\max\) 即可,复杂度为 \(O(n\log n)\)

code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
const int max1 = 5e5, B = 30;
const int inf = 0x3f3f3f3f;

int n, a, b;
struct Node
    { int next, v; } edge[max1 * 2 + 5];
int head[max1 + 5], total;
int father[max1 + 5], seq[max1 + 5], len;
bool vis[max1 + 5];

int f[max1 + 5], maxpos[max1 + 5], ans;
vector <int> tmp;

void Add ( int u, int v )
{
    edge[++total].v = v;
    edge[total].next = head[u];
    head[u] = total;
    return;
}

void Dfs ( int now, int fa, int pre_edge )
{
    father[now] = fa;
    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v;
        if ( v == fa )
            continue;
        Dfs(v, now, i);
    }
    return;
}

void Redfs ( int now, int fa )
{
    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v;
        if ( v == fa || vis[v] )
            continue;
        Redfs(v, now);
    }

    tmp.clear();
    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v;
        if ( v == fa || vis[v] )
            continue;
        tmp.push_back(f[v]);
    }
    sort(tmp.begin(), tmp.end());
    reverse(tmp.begin(), tmp.end());

    f[now] = 0; maxpos[now] = -1;
    
    int siz = tmp.size();
    for ( int i = 0; i < siz; i ++ )
    {
        if ( f[now] <= i + 1 + tmp[i] )
        {
            f[now] = i + 1 + tmp[i];
            maxpos[now] = tmp[i];
        }
    }
    return;
}

bool Check ( int mid )
{
    int A = f[seq[mid + 1]], B = f[seq[mid]];
    for ( int i = mid + 2; i <= len; i ++ )
        A = max(f[seq[i]] + ( A >= maxpos[seq[i]] ), A + 1);

    for ( int i = mid - 1; i >= 1; i -- )
        B = max(f[seq[i]] + ( B >= maxpos[seq[i]] ), B + 1);
    return A < B;
}

int Query ( int mid )
{
    int A = f[seq[mid + 1]], B = f[seq[mid]];
    for ( int i = mid + 2; i <= len; i ++ )
        A = max(f[seq[i]] + ( A >= maxpos[seq[i]] ), A + 1);

    for ( int i = mid - 1; i >= 1; i -- )
        B = max(f[seq[i]] + ( B >= maxpos[seq[i]] ), B + 1);
    return max(A, B);
}

int main ()
{
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);

    scanf("%d%d%d", &n, &a, &b); total = 1;
    for ( int i = 2, u, v; i <= n; i ++ )
    {
        scanf("%d%d", &u, &v);
        Add(u, v), Add(v, u);
    }

    Dfs(a, 0, 0);

    int now = b;
    while ( now )
    {
        seq[++len] = now;
        vis[now] = true;
        now = father[now];
    }

    for ( int i = 1; i <= len; i ++ )
        Redfs(seq[i], 0);

    int L = 1, R = len - 1, pos = len - 1;
    while ( L <= R )
    {
        int mid = L + R >> 1;
        if ( Check(mid) )
            pos = mid, R = mid - 1;
        else
            L = mid + 1;
    }

    ans = Query(pos);
    if ( pos > 1 )
        ans = min(ans, Query(pos - 1));

    printf("%d\n", ans);

    return 0;
}

T2 最小生成树

假设当前我们已经知道所有树边权值的相对大小关系,考虑统计非树边产生的贡献。

有一个经典结论:非树边连接端点 \(u,v\) 满足非树边权值大于 \(u,v\) 对应最小生成树的路径上边权最大值。

从边权较小的边向较大的边连边,可以得到拓扑关系图,去掉不必要的边后发现树边构成一条链,链上每个节点连接一些非树边,此时我们的目的是统计拓扑序的方案数,设每个节点下连接的非树边个数为 \(w_i\) ,考虑按照 \(w_i\) 个非树边, \(1\) 个树边的顺序依次插入到拓扑序中。

设当前加入的边的总数为 \(n\) ,树边有 \(b\) 个,方案数为 \(c\) 个,所有方案的树边位置和为 \(s\)

考虑加入一条树边,由于只能插入到序列开头,因此变化为:

\[(n,b,c,s)\to (n+1,b+1,c,s+c(b+1)) \]

考虑加入一条非树边,变化为:

\[(n,b,c,s)\to (n+1,b,c(n+1),s(n+2)) \]

简单解释一下 \(s\to s(n+2)\) ,首先一个基础的方案数的贡献为 \(s(n+1)\) ,之后树边所在的位置 \(A\) ,容易发现存在 \(A\) 种方案满足这个树边的位置 \(+1\) ,不难发现这部分的贡献和为 \(s\)

考虑加入 \(w_i\) 条非树边,变化为:

\[(n,b,c,s)\to (n+w_i,b,c(n+w_i)^{\underline{w_i}},s(n+w_i+1)^{\underline{w_i}}) \]

容易发现上述过程可以使用状压 dp 优化为 \(O(n2^n)\)

考虑快速求解 \(w_i\) ,容易发现 \(w_i\) 为添加边 \(i\) 后新增的满足端点位于同一连通块内的非树边的数量,考虑预处理 \(cnt_S\) 表示加入 \(S\) 集合内的树边后满足端点位于同一连通块内的非树边数量,此时很容易查询得到 \(w_i\) ,单独考虑每个非树边的贡献,设非树边连接两端点对应的树上路径的边集为 \(S\) ,容易发现这会贡献到 \(cnt_T,S\subseteq T\) ,使用 FWT 可以 \(O(n\log n)\) 预处理。

code
#include <cstdio>
#include <algorithm>
using namespace std;
const int max1 = 20, max2 = 400, max3 = 1 << 20;
const int mod = 1e9 + 7;

int n, m, u[max2 + 5], v[max2 + 5];
int inv[max2 + 5], fac[max2 + 5], ifac[max2 + 5];

struct Node
    { int next, v; } edge[max1 * 2 + 5];
int head[max1 + 5], total;
int father[max1 + 5], pre[max1 + 5], deep[max1 + 5];

int cnt[max3 + 5], f[max3 + 5], g[max3 + 5];

void Add ( int &x, int y )
{
    x += y;
    if ( x >= mod )
        x -= mod;
    return;
}

void Add_Edge ( int u, int v )
{
    edge[++total].v = v;
    edge[total].next = head[u];
    head[u] = total;
    return;
}

void Dfs ( int now, int fa, int pre_edge )
{
    father[now] = fa, pre[now] = pre_edge, deep[now] = deep[fa] + 1;
    for ( int i = head[now]; i; i = edge[i].next )
    {
        int v = edge[i].v;
        if ( v == fa )
            continue;
        
        Dfs(v, now, i >> 1);
    }
    return;
}

int Get_Edge ( int u, int v )
{
    int p = u, q = v;
    while ( p != q )
    {
        if ( deep[p] < deep[q] )
            swap(p, q);
        p = father[p];
    }

    int s = 0;
    while ( u != p )
    {
        s |= 1 << pre[u] - 1;
        u = father[u];
    }
    while ( v != p )
    {
        s |= 1 << pre[v] - 1;
        v = father[v];
    }
    return s;
}

int main ()
{
    freopen("mst.in", "r", stdin);
    freopen("mst.out", "w", stdout);

    scanf("%d%d", &n, &m);
    for ( int i = 1; i <= m; i ++ )
        scanf("%d%d", &u[i], &v[i]);
    total = 1;
    for ( int i = 1; i <= n - 1; i ++ )
        Add_Edge(u[i], v[i]), Add_Edge(v[i], u[i]);

    deep[0] = -1;
    Dfs(1, 0, 0);

    for ( int i = n; i <= m; i ++ )
        cnt[Get_Edge(u[i], v[i])] = 1;
    
    int lim = 1 << n - 1;
    for ( int i = 0; i <= n - 2; i ++ )
        for ( int j = 0; j < lim; j += 1 << i + 1 )
            for ( int k = 0; k < 1 << i; k ++ )
                cnt[( 1 << i ) + j + k] += cnt[j + k];
    
    inv[1] = 1;
    for ( int i = 2; i <= m; i ++ )
        inv[i] = 1LL * ( mod - mod / i ) * inv[mod % i] % mod;
    fac[0] = ifac[0] = 1;
    for ( int i = 1; i <= m; i ++ )
    {
        fac[i] = 1LL * fac[i - 1] * i % mod;
        ifac[i] = 1LL * ifac[i - 1] * inv[i] % mod;
    }

    f[lim - 1] = 0, g[lim - 1] = 1;
    
    for ( int s = lim - 1; s >= 0; s -- )
    {
        for ( int i = 1; i <= n - 1; i ++ )
        {
            if ( s >> i - 1 & 1 )
            {
                int w = cnt[s] - cnt[s ^ 1 << i - 1], sum = m - cnt[s] - __builtin_popcount(s);
                int upf = 1LL * f[s] * fac[sum + w + 1] % mod * ifac[sum + 1] % mod, upg = 1LL * g[s] * fac[sum + w] % mod * ifac[sum] % mod;

                Add(f[s ^ 1 << i - 1], ( upf + 1LL * upg * ( n - __builtin_popcount(s) ) ) % mod);
                Add(g[s ^ 1 << i - 1], upg);
            }
        }
    }

    printf("%d\n", f[0]);

    return 0;
}

T3 矩阵

容易发现 \(1\) 操作只会影响 \(y\) 坐标, \(2\) 操作只会影响 \(x\) 坐标,考虑一次询问 \((x,y)\) ,分别找到 \(x,y\) 所对应的操作时间 \(timx, timy\) ,容易发现 \((x,y)\) 位于第 \(\min(timx, timy)\) 次操作产生的行 / 列上,假设 \(timx<timy\) ,我们只需要找到 \(x\) 坐标在 \(timy\) 时刻的排名,容易发现这是对历史信息的查询,因此考虑可持久化平衡树,找到当前时刻 \(x\) 坐标所对应的节点,我们需要在 \(timy\) 时刻这个节点所对应的排名,由于可持久化后节点的 father 信息会发生改变,因此我们需要自上而下查询排名,考虑对于每个节点按照中序遍历维护一个权值,此时我们就是在 \(timy\) 时刻的平衡树上查询权值 \(val\) 的排名。由于存在插入行 / 列的操作,因此需要动态维护权值(否则 double 精度会爆炸),使用替罪羊树维护这个权值即可。

code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <iostream>
#include <random>
using namespace std;
const int max1 = 1e5, maxlen = 3e5;
const double alpha = 0.80, lim = 1e9, eps = 1e-10;
const unsigned int seed = 20051107;
mt19937 rnd((unsigned long long)&seed);

int Sint ( int L, int R )
    { return uniform_int_distribution <int> (L, R) (rnd); }

int n, p, q;

struct Scapegoat_Tree
{
    #define lson(now) tree[now].son[0]
    #define rson(now) tree[now].son[1]
    struct Struct_Scapegoat_Tree
    {
        int son[2], size;
        double val;
    }tree[max1 * 7 + 5];
    int root, total;
    int s[max1 * 7 + 5], top;

    void Clear ()
    {
        lson(0) = rson(0) = tree[0].size = tree[0].val = root = total = 0;
        return;
    }

    void Push_Up ( int now )
    {
        tree[now].size = tree[lson(now)].size + tree[rson(now)].size + 1;
        return;
    }

    bool Check ( int now )
    {
        return max(tree[lson(now)].size, tree[rson(now)].size) > tree[now].size * alpha;
    }

    void Dfs ( int now )
    {
        if ( !now )
            return;
        Dfs(lson(now));
        s[++top] = now;
        Dfs(rson(now));
        return;
    }

    int ReBuild ( int L, int R, double dl, double dr )
    {
        if ( L > R )
            return 0;

        int mid = L + R >> 1;
        tree[s[mid]].val = 0.5 * ( dl + dr );
        lson(s[mid]) = ReBuild(L, mid - 1, dl, tree[s[mid]].val);
        rson(s[mid]) = ReBuild(mid + 1, R, tree[s[mid]].val, dr);
        Push_Up(s[mid]);
        return s[mid];
    }

    int Build ( int L, int R, double dl, double dr )
    {
        if ( L > R )
            return 0;
        
        int mid = L + R >> 1;
        int now = ++total;
        tree[now].val = 0.5 * ( dl + dr );
        lson(now) = Build(L, mid - 1, dl, tree[now].val);
        rson(now) = Build(mid + 1, R, tree[now].val, dr);
        Push_Up(now);
        return now;
    }

    void Insert ( int &now, int k, double L, double R )
    {
        if ( !now )
        {
            now = ++total;
            lson(now) = rson(now) = 0;
            tree[now].size = 1;
            tree[now].val = 0.5 * ( L + R );
            return;
        }

        if ( tree[lson(now)].size >= k )
            Insert(lson(now), k, L, tree[now].val);
        else
            Insert(rson(now), k - tree[lson(now)].size - 1, tree[now].val, R);
        
        if ( Check(now) )
            { top = 0; Dfs(now); now = ReBuild(1, top, L, R); }
        Push_Up(now);
        return;
    }

    int Insert ( int k )
    {
        Insert(root, k, -lim, lim);
        return total;
    }
    
    double Query ( int now )
        { return tree[now].val; }
};

struct FHQ_Treap
{
    #define lson(now) tree[now].son[0]
    #define rson(now) tree[now].son[1]
    struct Struct_FHQ_Treap
    {
        int son[2], rd;
        int tim, id, size;
    }tree[max1 * 200 + 5];
    int root[max1 + 5], total;

    Scapegoat_Tree Map;
    
    void Clear ()
    {
        lson(0) = rson(0) = tree[0].rd = tree[0].tim = tree[0].id = tree[0].size = root[0] = total = 0;
        Map.Clear();
        return;
    }

    void Push_Up ( int now )
    {
        tree[now].size = tree[lson(now)].size + tree[rson(now)].size + 1;
        return;
    }

    void Split ( int now, int k, int &x, int &y )
    {
        if ( !now )
            { x = y = 0; return; }

        int clone = ++total;
        tree[clone] = tree[now];

        if ( tree[lson(clone)].size >= k )
            y = clone, Split(lson(clone), k, x, lson(y));
        else
            x = clone, Split(rson(clone), k - tree[lson(clone)].size - 1, rson(x), y);
        Push_Up(clone);
        return;
    }

    int Merge ( int x, int y )
    {
        if ( !x || !y )
        {
            if ( !x && !y )
                return 0;
            int clone = ++total;
            tree[clone] = tree[x | y];
            return clone;
        }

        if ( tree[x].rd > tree[y].rd )
        {
            int clone = ++total;
            tree[clone] = tree[x];
            rson(clone) = Merge(rson(clone), y);
            Push_Up(clone);
            return clone;
        }

        int clone = ++total;
        tree[clone] = tree[y];
        lson(clone) = Merge(x, lson(clone));
        Push_Up(clone);
        return clone;
    }

    void Insert ( int num, int k, int tim )
    {
        k += maxlen;

        int x, y;
        Split(root[num], k - 1, x, y);

        int New = ++total;
        lson(New) = rson(New) = 0;
        tree[New].rd = Sint(0, 2e9);
        tree[New].tim = tim;
        tree[New].id = Map.Insert(tree[x].size);
        tree[New].size = 1;

        root[num] = Merge(Merge(x, New), y);

        return;
    }

    int Build ( int L, int R, int tim )
    {
        if ( L > R )
            return 0;
        
        int mid = L + R >> 1;
        int now = ++total;
        tree[now].rd = Sint(0, 2e9);
        tree[now].tim = tim;
        tree[now].id = now;
        lson(now) = Build(L, mid - 1, tim);
        rson(now) = Build(mid + 1, R, tim);
        Push_Up(now);
        return now;
    }

    void Build ()
    {
        root[0] = Build(1, maxlen + maxlen + 1, 0);
        Map.root = Map.Build(1, maxlen + maxlen + 1, -lim, lim);
        return;
    }

    int Kth_size ( int now, int k )
    {
        if ( k == tree[lson(now)].size + 1 )
            return now;
        else if ( k <= tree[lson(now)].size )
            return Kth_size(lson(now), k);
        return Kth_size(rson(now), k - tree[lson(now)].size - 1);
    }

    int Kth_val ( int now, double d )
    {
        if ( fabs(Map.Query(tree[now].id) - d) < eps )
            return tree[lson(now)].size + 1;
        if ( Map.Query(tree[now].id) > d )
            return Kth_val(lson(now), d);
        return tree[lson(now)].size + 1 + Kth_val(rson(now), d);
    }

    int Kth ( int num, int v, int k )
    {
        k += maxlen;
        int u = Kth_size(root[num], k);

        return Kth_val(root[v], Map.Query(tree[u].id));
    }

    int Query_Tim ( int now, int k )
    {
        if ( k == tree[lson(now)].size + 1 )
            return tree[now].tim;
        if ( k <= tree[lson(now)].size )
            return Query_Tim(lson(now), k);
        return Query_Tim(rson(now), k - tree[lson(now)].size - 1);
    }

    int Query ( int num, int k )
    {
        k += maxlen;
        return Query_Tim(root[num], k);
    }
}Treex, Treey;

void Query ( int num, int x, int y )
{
    int timx = Treex.Query(num, x);
    int timy = Treey.Query(num, y);

    if ( timx > timy )
    {
        p = timx, q = Treey.Kth(num, timx, y) - maxlen;
        printf("%d %d\n", p, q);
    }
    else if ( timx < timy )
    {
        p = Treex.Kth(num, timy, x) - maxlen, q = timy;
        printf("%d %d\n", p, q);
    }
    else
    {
        p = Treex.Kth(num, 0, x) - maxlen, q = Treey.Kth(num, 0, y) - maxlen;
        printf("%d %d\n", p, q);
    }
    return;
}

int main ()
{
    freopen("matrix.in", "r", stdin);
    freopen("matrix.out", "w", stdout);

    int opt, x, y;
    scanf("%d", &n);

    Treex.Clear(), Treey.Clear();
    Treex.Build(), Treey.Build();
    p = q = 0;

    for ( int i = 1; i <= n; i ++ )
    {
        Treex.root[i] = Treex.root[i - 1];
        Treey.root[i] = Treey.root[i - 1];
        scanf("%d%d", &opt, &x);
        if ( opt == 1 )
        {
            x += p + q;
            Treey.Insert(i, x, i);
        }
        else if ( opt == 2 )
        {
            x += p + q;
            Treex.Insert(i, x, i);
        }
        else
        {
            scanf("%d", &y);
            x += p + q, y += p - q;
            Query(i, x, y);
        }
    }

    return 0;
}
posted @ 2023-05-17 17:19  KafuuChinocpp  阅读(15)  评论(0)    收藏  举报