[JOISC 2021 Day4] 最悪の記者 4 题解

· · 题解

考虑 ia_i 连边,构成一个内向基环树,注意到环上的人的 Rating 都应相等,而树上祖先的 Rating 都比后代小。

假设只有一棵树的情况。注意到这样一个事实:

当确定好不更改哪一些人的 Rating 时,如果这些人的 Rating 满足了他们之间已经有了的限制关系,剩下的人通过更改 Rating 一定能够满足题意。

问题转化成了保留一定数量的点满足限制关系,且这些点的权值和最大,想到树形 DP。

f_u 表示第 u 个人必须选,其子树中的选择的点权值和的最大值。

f_u=c_u+\max_{S\in subtree(u)/\{u\}}\sum_{v \in S} f_v

其中 S 是两两没有祖先关系且 Rating 都大于 h_u 的一些点的集合。

发现这些点的选取跟 h_u 有关,把 h_u 离散化后记这些转移式右边那坨为 g_{u,i},表示仅考虑那些 h_v\geq i 的点的答案。

这样子就 f_u=c_u+g_{u,h_u} 了。

转移 g 数组就把各个儿子对应位置的值相加,再与父亲的 DP 值 [1,h_u] 前缀取 max 即可,线段树合并维护。

现在线段树要支持区间取 max 和合并,也就是说要取 max 还要加,想了很久。

后来问了同机房数据结构神仙才知道要维护两个懒标记,像线段树2一样先 max 后 +,本质上是广义矩阵乘法,写一个 struct 复合懒标记即可。

至于环上的话枚举将值修改成环上的哪一个值或者是全局最小值,取最大值统计答案。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
const int _=200003;
int n,cnt;
int a[_],h[_],c[_],t[_];
int read(){
    char c=getchar();int x=0;
    while (c<48||c>57) c=getchar();
    do x=(x<<1)+(x<<3)+(c^48),c=getchar();
    while (c>=48&&c<=57);
    return x;
}
typedef long long ll;
struct tags{ll a,c;tags(ll A=0,ll C=0):a(A),c(C){}};
tags operator*(const tags x,const tags y){
    return tags(max(x.a,y.a-x.c),x.c+y.c); //这里需要稍微推下式子
}
int hd[_],ver[_],nxt[_],tot;
void add(int u,int v){nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;}
tags sg[_<<6];
int lc[_<<6],rc[_<<6];
void pushdown(int p){
    if (sg[p].a||sg[p].c){
        if (!lc[p]) lc[p]=++cnt;
        sg[lc[p]]=sg[lc[p]]*sg[p];
        if (!rc[p]) rc[p]=++cnt;
        sg[rc[p]]=sg[rc[p]]*sg[p];
        sg[p]=tags();
    }
}
ll query(int x,int &p,int l=1,int r=n){
    if (!p) p=++cnt;
    if (l==r) return sg[p].a+sg[p].c;
    pushdown(p);
    int mid=(l+r)>>1;
    if (x<=mid) return query(x,lc[p],l,mid);
    else return query(x,rc[p],mid+1,r);
}
void modify(int x,ll v,int &p,int l=1,int r=n){
    if (!p) p=++cnt;
    if (x>=r) {sg[p]=sg[p]*tags(v,0);return;}
    pushdown(p);
    int mid=(l+r)>>1;
    modify(x,v,lc[p],l,mid);
    if (x>mid) modify(x,v,rc[p],mid+1,r);
}
int merge(int u,int v){
    if (!u||!v) return u^v;
    if (!lc[u]&&!rc[u]) u^=v^=u^=v;
    if (!lc[v]&&!rc[v]) sg[u]=sg[u]*tags(0,sg[v].a+sg[v].c);
    //每一次都pushdown强行新建节点复杂度会爆炸,所以没有儿子节点时改为添加加法懒标记
    else pushdown(u),pushdown(v);
    lc[u]=merge(lc[u],lc[v]);
    rc[u]=merge(rc[u],rc[v]);
    return u;
}
int rt[_];ll buc[_];
bool vis[_],cir[_],tmp[_];
void dfs(int u){
    for (int i=hd[u]; i; i=nxt[i]){
        int v=ver[i];
        if (cir[v]) continue;
        dfs(v);
        rt[u]=merge(rt[u],rt[v]);
    }
    ll v=query(h[u],rt[u])+c[u];
    if (!cir[u]) modify(h[u],v,rt[u]);
}
int main(){
    ll sum=0; n=read();
    for (int i=1; i<=n; ++i){
        a[i]=read(); t[i]=h[i]=read();
        sum+=c[i]=read(); add(a[i],i);
    }
    sort(t+1,t+n+1);
    int rk=unique(t+1,t+n+1)-t-1;
    for (int i=1; i<=n; ++i) h[i]=lower_bound(t+1,t+rk+1,h[i])-t;
    for (int i=1; i<=n; ++i)
        if (!vis[i]){
            int cur=i;
            while (!vis[cur]) tmp[cur]=vis[cur]=1,cur=a[cur];
            if (tmp[cur]) while (!cir[cur]) cir[cur]=1,cur=a[cur];
            cur=i;while (tmp[cur]) tmp[cur]=0,cur=a[cur];
        }
    for (int i=1; i<=n; ++i) if (cir[i]) dfs(i);
    for (int i=1; i<=n; ++i)
        if (cir[i]){
            int cur=i;
            while (cir[cur]){
                cir[cur]=0;
                if (cur^i) rt[i]=merge(rt[i],rt[cur]);
                buc[h[cur]]+=c[cur];
                cur=a[cur];
            }
            ll res=query(1,rt[i]);
            while (!tmp[cur]){
                tmp[cur]=1;
                res=max(res,query(h[cur],rt[i])+buc[h[cur]]);
                cur=a[cur];
            }
            sum-=res;
            while (tmp[cur]){
                tmp[cur]=0;
                buc[h[cur]]=0;
                cur=a[cur];
            }
        }
    printf("%lld\n",sum);
    return 0;
}

时间复杂度 \mathcal{O}(n\log_2n)