【考试总结】2022-01-19

中心城镇问题

不难得到一种复杂度为 Θ(nk) 的做法,对 DP 数组维护后缀最大值即可

注意转移时如果钦定距离当前点最小距离为 k 时,当前归并的子树和已经归并的子树中的下标选择都不应该小于 k

再观察这个做法地冗余在于子树高度和 k 差很多时很多状态时无意义的,所以使用长链剖分优化即可

实现长链剖分主要是将数组用指针优化,减少继承长儿子时的运算但是要注意选择自己的时候也要加上长儿子上对应的后缀最大值

比较离奇的是每个点的状态不应该开到 min(len[x],K) 而是 len[x],否则会错

Code Display
const int N=1e6+10;
int n,k,w[N];
vector<int> g[N];
int tmp[N],len[N],son[N];
int dp[N*4],Mx[N*4],*f[N],*sufx[N],*fst=dp,*mxst=Mx;
inline void dfs(int x,int fat){
    for(int t:g[x]) if(t^fat){
        dfs(t,x);
        if(len[t]>len[son[x]]) son[x]=t;
    }
    len[x]=len[son[x]]+1;
    return ;
}
inline int Suf(int x,int pos){
    if(pos>len[x]||pos<0) return 0ll;
    return sufx[x][pos];
}
inline void Dp(int x,int fat){
    if(son[x]){
        f[son[x]]=f[x]+1;
        sufx[son[x]]=sufx[x]+1;
        Dp(son[x],x);
    }
    f[x][0]=w[x]+Suf(son[x],k-1); sufx[x][0]=max(Suf(x,1),w[x]+Suf(son[x],k-1));
    for(int t:g[x]) if(t!=fat&&t!=son[x]){
        sufx[t]=mxst; f[t]=fst; fst+=len[t]<<1; mxst+=len[t]<<1;
        Dp(t,x);
        for(int i=0;i<=len[t]+1;++i){
            ckmax(tmp[i],f[x][i]+Suf(t,max(i-1,k-i-1))); 
            if(i>0) ckmax(tmp[i],f[t][i-1]+Suf(x,max(i,k-i)));
        }
        for(int i=len[t]+1;i>=0;--i) sufx[x][i]=max(Suf(x,i+1),f[x][i]=tmp[i]),tmp[i]=0;
    }
    return ;   
}
signed main(){
    freopen("central.in","r",stdin); freopen("central.out","w",stdout);
    n=read(); k=1+read(); rep(i,1,n) w[i]=read();
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        g[u].pb(v); g[v].pb(u);
    }
    dfs(1,0);
    sufx[1]=mxst; f[1]=fst;
    mxst+=len[1]<<1; fst+=len[1]<<1;
    Dp(1,0);
    int ans=0;
    for(int i=0;i<=len[1];++i) ckmax(ans,f[1][i]);
    print(ans);
    return 0;
}

心理阴影

容易找到的 Θ(poly(n)) 的做法是在两个点的 LCA 处计算两个点逆序的概率

这时概率可以使用逆序方案除以总方案数来算,而相对顺序的总合并方案是 (sizx+sizysizy)

设两个点 x,yLCA 处的两向儿子分别为 sx,sy,可以计算出 xsx 的子树里面排位为 oy 的概率,y 也要做这样的工作

那么枚举两个点在原排列中的排位,新序列中的排位,在新序列中两个点排位之间的两个原排列中数字的数量再组合数即可

满分做法非常巧妙,我们计算 fu,v 表示 (u,v) 两个点的子树合并后逆序对的平均数,枚举整个序列是 u 在首位还是 v 在首位

直接的贡献是 lequ,v/leqv,u 表示在 u/v 的子树中小于等于 v/u 的数的数量,剩下的问题是原问题的子问题,递归求解即可

u 在首位的概率是 sizusizv+sizu,计算方法是钦定 u 在首位时剩下点分布的组合数比总方案数

Code Display
const int N=5010;
int ans,rt,n,fa[N],siz[N],inv[N];
vector<int> g[N],sub[N];
int leq[N][N];
inline void dfs(int x,int fat){
    fa[x]=fat; sub[x].pb(x);
    siz[x]=1; rep(i,x,n) leq[x][i]++;
    for(int i:g[x]) if(i!=fat){
        dfs(i,x),siz[x]+=siz[i];
        rep(j,1,n) leq[x][j]+=leq[i][j];
        for(auto t:sub[i]) sub[x].pb(t),ans+=x>t;
    }
    return ;
}
int f[N][N];
inline int calc_f(int u,int v){
    if(~f[v][u]) return f[v][u];
    if(~f[u][v]) return f[u][v]; f[u][v]=add(mul(siz[u],leq[v][u]),mul(siz[v],leq[u][v]));
    for(auto t:g[u]) if(t^fa[u]){
        //u in the front
        ckadd(f[u][v],mul(siz[u],calc_f(t,v)));
    }
    for(auto t:g[v]) if(t^fa[v]){
        //v in the front
        ckadd(f[u][v],mul(siz[v],calc_f(t,u)));
    }
    return f[u][v]=mul(f[u][v],inv[siz[u]+siz[v]]);
}
inline void get_ans(int x){
    for(auto t:g[x]) if(t!=fa[x]) get_ans(t);
    int siz=g[x].size();
    rep(i,0,siz-1) if(g[x][i]^fa[x]) rep(j,i+1,siz-1) if(g[x][j]^fa[x]) ckadd(ans,calc_f(g[x][i],g[x][j]));
    return ;
}
signed main(){
    freopen("nightmare.in","r",stdin); 
    freopen("nightmare.out","w",stdout);
    n=read(); rt=read();
    inv[0]=inv[1]=1;
    for(int i=2;i<=n;++i) inv[i]=mod-mul(mod/i,inv[mod%i])%mod;
    rep(i,1,n-1){
        int u=read(),v=read();
        g[u].pb(v); g[v].pb(u);
    } dfs(rt,0);
    memset(f,-1,sizeof(f));
    get_ans(rt);
    print(ans);
    return 0;
}

pajel 游戏

主要思想是可以使用“右手扶墙”这一做法来将每个四联通的连通块周围所有的有颜色的格子取出

要减少连通块,那么这些有颜色的格子中排位相邻但是在原图中不相邻的格子对要连起来

这时候这个连通块剩下的空格全填一个颜色即可,填入哪个颜色是等价的

由于实现代码造成的正确性并不高,所以我完整手玩了 10 个测试点,重复玩了 6,10 个测试点

手玩的思想也是完整执行右手扶墙

感谢 401rk8 和 zero4338 的帮助,手玩了一整天,本来要写一份工作报告的,但是提交文件没了就不写了

Code Display
Display 呢?提答题要什么Code

还是完全人脑图灵机的提答题
posted @   没学完四大礼包不改名  阅读(85)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示