首页 > 其他分享 >「模拟赛20220924」巧立名目

「模拟赛20220924」巧立名目

时间:2022-09-25 08:44:09浏览次数:52  
标签:const 20220924 int rep beg varphi 巧立名目 Read 模拟

题目

给定一棵 \(n\) 个结点的树。现在有 \(k\) 个人,每个人最初在 \(s_k\),最终必须到达 \(t_k\)。

每个结点有一个势能 \(\varphi\)。某一时刻,如果 \(k\) 个人的位置分别为 \(p_1,p_2,\dots,p_k\),则当前局面的势能为 \(\Phi=\sum_k\varphi_{p_k}\)。

每一个时刻,你可以将某个人从他当前所在结点移动到树上相邻的一个结点上,直到每个人都到达了自己的目标。最小化这个过程中(包括初始和最后的时刻)的 \(\Phi\) 的最大值。

对于 \(100\%\) 的数据,满足 \(1\le n\le 5\times 10^3,0\le k\le 5\times 10^3\)。

分析

认识到一点:这个过程中我们有可能会让某个人 \(k\) 走出从 \(s_k\) 到 \(t_k\) 的最短路。这是因为,我们可能需要让某些人走到 \(\varphi\) 较小的结点上,从而缓冲另一个人路途上可能经过的极大的 \(\varphi\)。

还是不够具象。由于我们需要最小化任意一个时刻的 \(\Phi\),所以我们必然需要考虑 \(s\rightarrow t\) 上的 \(\max\)(实际上我们也只需要关心 \(\max\))。从这一点切入,我们借助 Kruskal 重构树(点权、大根)来分析。

Note.

要点之一。因为我们关心的是每个人走过的最大值,所以可以用点权最大的 Kruskal 重构树来表征可以走到的连通块。

\(s\rightarrow t\),首先需要 \(s\rightarrow \text{LCA}(s,t)\)。因此把问题拆开来,每个人首先爬到 \(\text{LCA}(s,t)\) 及以上。这就只需要向上跳了。

Note.

另一个要点。如果我们始终考虑到 \(s\rightarrow t\) 的限制,那么走起来会处处掣肘,所以最好先弱化一下问题。而根据路径“先上后下”的结构,自然我们应当先考虑前半部分怎么上跳。

向上跳必然会导致答案增大。设 \(\varphi_u^*\) 为 Kruskal 重构树上 \(u\) 子树内点权最小值,那么初始能够达到的最小状态就是 \(\sum_k\varphi^*_{p_k}\)。此时某个人 \(t\) 上跳的结果就是 \(\sum_k\varphi^*_{p_k}+(\varphi_{\texttt{fa}_t}-\varphi^*_t)\)。注意到,我们已经改写成增量的形式,这样便于分离变量。

很显然,在这之后我们肯定是每次选择 \(\varphi_{\texttt{fa}_t}-\varphi^*_t\) 最小的 \(t\) 进行上跳。持续这样进行,我们最终可以得到一个状态,使得每个人都可以单独到达自己的 \(\text{LCA}\)(但不一定可以一起到达)。显然这是一个必须达到的状态。

反过来,怎么构造后半部分?注意到终止状态完全确定,并且过程存在对称性,所以我们可以构造 \(t\rightarrow \text{LCA}(s,t)\) 的一段路径,并且达到类似的状态。

如果可以将两部分接起来,我们就完成了。而这一条是显然的,比如我们可以将正向构造出的状态调整得和反向构造出的终止状态一样。

因此,用一个堆或者 zkw 线段树维护即可。复杂度为 \(O(nk\log n)\)。

代码

#include <cstdio>
#include <algorithm>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

const LL INF = 1e18;
const int MAXN = 5e3 + 5;

template<typename _T>
inline void Read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) Write( x / 10 );
    putchar( x % 10 + '0' );
}

template<typename _T>
inline _T Min( const _T &a, const _T &b ) {
    return a < b ? a : b;
}

template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
    return a > b ? a : b;;
}

struct MyGraph {
    struct Edge {
        int to, nxt;
    } Graph[MAXN << 1];

    int head[MAXN], cnt;

    inline Edge operator [] ( const int &idx ) const { return Graph[idx]; }

    inline void AddE( const int &from, const int &to ) {
        AddEdge( from, to ), AddEdge( to, from );
    }

    inline void AddEdge( const int &from, const int &to ) {
        Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
        head[from] = cnt;
    }
};

MyGraph tre, kru;

int fath[MAXN], dep[MAXN], mn[MAXN];

int fr[MAXN], to[MAXN], lca[MAXN];
int val[MAXN], seq[MAXN];

int N, K;

namespace UFS {
    int fa[MAXN];

    inline void MakeSet( const int &n ) {
        rep( i, 1, n ) fa[i] = i;
    }

    int FindSet( const int &u ) {
        return fa[u] == u ? u : ( fa[u] = FindSet( fa[u] ) );
    }
}

namespace Process {
    LL delt[MAXN];
    int mnIdx[MAXN << 2];
    int bas;

    int beg[MAXN], ed[MAXN];
    bool sati[MAXN];

    inline void Upt( const int &x ) {
        mnIdx[x] = delt[mnIdx[x << 1]] <= delt[mnIdx[x << 1 | 1]] ? mnIdx[x << 1] : mnIdx[x << 1 | 1];
    }

    inline void Build( const int &n ) {
        for( bas = 1 ; bas < n ; bas <<= 1 );
        delt[0] = INF;
        rep( i, 1, n ) {
            int x = i + bas - 1, u = beg[i];
            mnIdx[x] = i, delt[i] = fath[u] ? val[fath[u]] - mn[u] : INF;
        }
        rep( i, bas + n, bas << 1 | 1 ) mnIdx[i] = 0;
        per( i, bas - 1, 1 ) Upt( i );
    }

    inline void Modify( const int &i ) {
        int x = i + bas - 1, u = beg[i];
        delt[i] = fath[u] ? val[fath[u]] - mn[u] : INF;
        for( x >>= 1 ; x ; x >>= 1 ) Upt( x );
    }

    LL Solve() {
        int ever = 0;
        LL mnSu = 0, ret = 0;
        rep( i, 1, K ) {
            ever += sati[i] = beg[i] == ed[i];
            mnSu += mn[beg[i]], ret += val[beg[i]];
        }
        Build( K );
        while( ever < K ) {
            int i = mnIdx[1];
            ret = Max( ret, mnSu + delt[i] ), mnSu -= mn[beg[i]];
            if( ( beg[i] = fath[beg[i]] ) == ed[i] )
                sati[i] = true, ever ++;
            mnSu += mn[beg[i]], Modify( i );
        }
        return ret;
    }
}

void Init( const int &u, const int &fa ) {
    dep[u] = dep[fa] + 1, mn[u] = val[u];
    for( int i = kru.head[u], v ; i ; i = kru[i].nxt )
        if( ( v = kru[i].to ) ^ fa )
            Init( v, u ), mn[u] = Min( mn[u], mn[v] );
}

inline int LCA( int u, int v ) {
    while( u ^ v ) {
        if( dep[u] < dep[v] ) std :: swap( u, v );
        u = fath[u];
    }
    return u;
}

int main() {
    Read( N );
    rep( i, 1, N ) Read( val[i] );
    rep( i, 1, N - 1 ) {
        int u, v; Read( u ), Read( v );
        tre.AddE( u, v );
    }
    Read( K );
    rep( i, 1, K ) Read( fr[i] ), Read( to[i] );
    rep( i, 1, N ) seq[i] = i;
    auto Cmp = 
        [] ( const int &a, const int &b ) -> bool {
            return val[a] == val[b] ? a < b : val[a] < val[b];
        };
    std :: sort( seq + 1, seq + 1 + N, Cmp );
    UFS :: MakeSet( N );
    rep( i, 1, N ) {
        int u = seq[i];
        for( int j = tre.head[u], v ; j ; j = tre[j].nxt )
            if( Cmp( v = tre[j].to, u ) ) {
                v = UFS :: FindSet( v );
                fath[v] = u, UFS :: fa[v] = u;
                kru.AddE( u, v );
            }
    }
    Init( seq[N], 0 );
    rep( i, 1, K ) {
        lca[i] = LCA( fr[i], to[i] );
        Process :: beg[i] = fr[i];
        Process :: ed[i] = lca[i];
    }
    LL ans = Process :: Solve();
    rep( i, 1, K ) Process :: beg[i] = to[i];
    Write( Max( ans, Process :: Solve() ) ), putchar( '\n' );
    return 0;
}

标签:const,20220924,int,rep,beg,varphi,巧立名目,Read,模拟
From: https://www.cnblogs.com/crashed/p/16727220.html

相关文章

  • 20220924模拟赛解题报告
    概要我AK了,srds因为有Q老师的免费测试套餐,才发现题目名错了(。题目难度不大题目T1随便脚算一下人数完了比赛后\(-\)比赛前\(+\)晋级的。code:#include<bits/......
  • 20220924--CSP-S模拟10
    A.欧几里得的噩梦首先发现第一问所询问的异或值数量就是所求的第二问的最小集合的元素个数次方因为除去集合里的任一一个元素,其中若干个元素异或之后的集合就不可能为原......
  • CSP-S模拟10
    T1.欧几里得的噩梦第一眼,这不是线性基板子题吗。但是值域是\(2^{5e5}\),但是我们发现它的一个神奇性质,一个数的二进制中只有两个一。我们定义高位为x,低位为y。如果线性基中......
  • CSP-S模拟10
    体活就是用来上体育的~~~Cat到操场上跑了n圈,6:00~6:25,并且边跑边唱歌,就是那首我最喜欢的"世界问,你是谁,来自哪……"好像路上有人因此回头多看了两眼,今天的云好美,一路上的老......
  • 9.24-CSP-S模拟10
    T1欧几里得的噩梦一眼线性基题,可以证明在模拟线性基插入时,任何时候当前数的为1的位都不会超过二,于是模拟的做法就很显然了。但是这显然复杂度还是错的,赛时百分之八十的人......
  • 2022NOIP前模拟赛订正情况
    √表示已订正,×表示不在能力范围之内,空表示未订正日期ABCD订正地址2022.9.3√√√×https://www.luogu.com.cn/contest/828672022.9.7√××ht......
  • Solution - 「OurOJ #47407」巧立名目
    \(\mathscr{Description}\)  Privatelink.  给定一棵含有\(n\)个点的带点权树和大小为\(m\)的有序点对集合\(\{(s_i,t_i)\}_{i=1}^n\).每次操作可以选择一个......
  • [总结]2022.9.24 挖土机杯 CSP-J 组模拟赛 R1
    [总结]2022.9.24挖土机杯CSP-J组模拟赛R1P1赛时情况看到T1,显然是道白给。但我想了一会。依旧先把题目读完。T2有点模拟的样子,但又有点简单;T3显然dp;T4连乱搞都不会......
  • 队列的模拟及环形队列思路
    定义队列是一个有序列表,可以用数组或是链表来实现。遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出模拟思路队列本身是有序列表,若使用数组的......
  • CSP模拟10
    现在有这样一种感觉:是在留下永远不会在有人看的遗产。T1正解并查集,直接把每次给你的\(x,y\)用并查集合并一下(没有\(y\)就把\(x\)和\(0\)合并一下)并加入答案,如果......