ZROI Contest 350

发布于 25 天前  65 次阅读


B

现在有一棵树 要求对于每个点 $x$ ,求所有点到他的链的最大点权之和
$n \leq 4 \times 10^5$

题解:又是我不会的思博题。
这种题目我们可以首先考虑在链上的做法:
显然有一种基于分治的方法,但是很难扩展到树上。
所以我们考虑如果变成边权是不是好维护。。(
我们考虑定义点 $i$ 和 $i+1$ 之间的边的边权是 $max\{a_{i},a_{i+1}\}$。然后变成了求两点之间边权最大值。我们考虑将边从小到大排序,每次加入一条新边,对于边左端点的连通块,答案都会加上一个边权乘右边端点连通块的大小。右面答案贡献同理。
所以我们如果放在树上也是差不多做。可以维护处所有连通块的大小,然后每次加边相当于是一个子树加贡献操作。
我们如果直接并查集路径压缩就会自闭。我们必须要维护出树的形态。第一种情况是使用带权并查集。注意要额外减去合并时多余的贡献。
一个更简单的做法是使用重构树:这样我们直接在点上打标记就可以了

/*
 * Author: RainAir
 * Time: 2019-07-26 15:20:25
 */
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <bitset>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <map>
#include <set>

#define fi first
#define se second
#define U unsigned
#define P std::pair
#define Re register
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 1e6 + 5;
std::vector<int> G[MAXN];

struct Edge{
    int u,v,w;

    bool operator < (const Edge &t) const {
        return w < t.w;
    }
}e[MAXN];
int n;
int p[MAXN],v[MAXN],f[MAXN],sm[MAXN],sz[MAXN],val[MAXN],cnt;

inline int find(int x){
    return x == f[x] ? x : f[x] = find(f[x]);
}

inline void merge(int x,int y){
    int fx = find(x),fy = find(y);
    G[++cnt].pb(fx);G[cnt].pb(fy);
    G[fx].pb(cnt);G[fy].pb(cnt);
    f[fx] = f[fy] = cnt;
    sz[cnt] = sz[fx]+sz[fy];
}

int ans[MAXN];

inline void dfs(int v,int fa=0,int sum=0){
    sum += sm[v];ans[v] = sum;
    for(auto x:G[v]){
        if(x == fa) continue;
        dfs(x,v,sum);
    }
}

signed main(){
    scanf("%lld",&n);cnt = n;
    FOR(i,2,n) scanf("%lld",p+i);
    FOR(i,1,n) scanf("%lld",val+i);
    std::fill(sz,sz+n+1,1);
    FOR(i,2,n) e[i-1] = (Edge){i,p[i],std::max(val[p[i]],val[i])};
    std::sort(e+1,e+n);FOR(i,1,2*n) f[i] = i;
    FOR(i,1,n-1){
        int u = e[i].u,v = e[i].v,w = e[i].w;
        int fu = find(u),fv = find(v);
        sm[fu] += sz[fv]*w;sm[fv] += sz[fu]*w;
        merge(fu,fv);
    }
    int rt = find(1);
    dfs(rt);
    FOR(i,1,n) printf("%lld ",ans[i]+val[i]);puts("");
    return 0;
}

C

遇到这种删数的问题我们可以首先考虑最后一个删除的数是什么/对答案的影响。
发现如果我们钦定删除了某一个点,它的值是 $x$,那么这个点会将当前区间分裂成两个区间,并且要保证左区间删除的时候不能有一时刻最右边的点的值为 $x$,右边区间同理。
所以我们可以考虑设计出一个区间 dp:$f_{l,r,lk,rk}$ 表示删除区间 $l,r$,强制不让某一时刻的左边为 $lk$,也不让某一时刻右边为 $rk$.转移显然:
$$f_{l,r,lk,rk} = \sum_{k = l}^r \left(^{r-l}_ {k-l}\right)f_{l,k-1,lk,a_k}\times f_{k+1,r,a_k,rk}$$
总共是 $O(n^5)$ 的。
转移发现 $lk = a_{l-1},rk = a_{r+1}$,所以变成 $O(n^3)$。

/*
 * Author: RainAir
 * Time: 2019-07-26 16:04:47
 */
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <bitset>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <map>
#include <set>

#define fi first
#define se second
#define U unsigned
#define P std::pair
#define Re register
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 1000+5;
const int ha = 998244353;
int f[MAXN][MAXN];
int n,a[MAXN];
int fac[MAXN],inv[MAXN];

inline int qpow(int a,int n=ha-2){
    int res = 1;
    while(n){
        if(n & 1) res = 1ll*res*a%ha;
        a = 1ll*a*a%ha;
        n >>= 1;
    }
    return res;
}

inline void init(){
    fac[0] = 1;
    FOR(i,1,MAXN-1) fac[i] = 1ll*fac[i-1]*i%ha;
    inv[MAXN-1] = qpow(fac[MAXN-1]);
    ROF(i,MAXN-2,0) inv[i] = 1ll*inv[i+1]*(i+1)%ha;
}

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

signed main(){
    init();
    scanf("%lld",&n);FOR(i,1,n) scanf("%lld",a+i);
    FOR(i,2,n){
        if(a[i] == a[i-1]){
            puts("0");return 0;
        }
    }
    FOR(i,1,n) f[i][i] = 1;
    FOR(i,1,n+1) f[i][i-1] = 1;
    a[0] = a[n+1] = -1;
    FOR(len,2,n){
        FOR(l,1,n){
            int r = l+len-1;
            if(r > n) break;
            FOR(k,l,r){
                if(a[k] != a[l-1] && a[k] != a[r+1])
                    (f[l][r] += 1ll*f[l][k-1]*f[k+1][r]%ha*C(r-l,k-l)) %= ha;
            }
        }
    }
    printf("%lld\n",f[1][n]);
    return 0;
}

一个OIer。