AtCoder Beginner Contest 329G题解

abc329题解

G - Delivery on Tree

题意:一棵二叉树,上面有M个邮递任务,要求把在 S i S_i Si的物体运送到 T i T_i Ti,邮包大小为K,从根节点出发回到根节点,每条边必须且只能来回走一次,问有多少走法。

解答:可以看出,从根节点出发,方案数就是左子树走法乘上右子树走法+右子树走法乘上左子树走法,但是需要看到如果左子树有邮包需要从右子树运过来,那就必须先走右子树,同理左子树。

递归搜索的状态肯定是 ( u , j ) (u,j) (u,j),u代表当前位置,j代表当前包内邮件数量。进入该状态后,首先应当减去祖先给该节点运送的邮包数量n,于是 j ′ = j − n j'=j-n j=jn,然后我们枚举所有可行的走的顺序(先走右,先走左),根据该顺序来递归搜索更新答案。要用记忆化搜索来保证复杂度为 O ( N K ) O(NK) O(NK)

一个难点是怎么知道传递给子树搜索时包内邮件数量怎么变化。若邮件从s到t,

1.lca(s,t)=s s往t方向子树走时要带上这封邮件(即邮件数量+1),t的父亲往t走时完成运送(邮件数量-1)

2.lca(s,t)=t s往父亲方向走时带上邮件,s方向子树根节点到t节点时运送完成

3.其它 s往父亲方向走时带上邮件,t的父亲往t走时完成运送

前期先预处理这些信息,后面搜索时用,要注意每一步搜索的合法性,中间若有任何一个时刻邮包数量>K都是不可行的。

代码:

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
constexpr int P = 998244353;
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    vector par(n,vector<int>(19,-1));
    vector<int> dep(n,-1);
    vector<vector<int>> adj(n);
    for(int i=1;i<n;i++){
        int p;
        cin>>p;
        p--;
        par[i][0]=p;
        adj[p].push_back(i);
    }
    dep[0]=0;
    function<void(int)> dfs=[&](int x){
        for(auto y:adj[x]){
            dep[y]=dep[x]+1;
            dfs(y);
        }
        return;
    };
    auto lcaInit=[&](){
        for(int i=1;i<19;i++){
            for(int j=0;j<n;j++){
                if(dep[j]<1<<i){
                    continue;
                }
                par[j][i]=par[par[j][i-1]][i-1];
            }
        }
        return;
    };
    auto up=[&](int u,int d){
        for(int i=0;i<19;i++){
            if(d>>i&1){
                u=par[u][i];
            }
        }
        return u;
    };
    auto lca=[&](int u,int v){
        if(dep[u]>dep[v]){
            swap(u,v);
        }
        v=up(v,dep[v]-dep[u]);
        if(u==v){
            return u;
        }
        for(int i=18;i>=0;i--){
            if(par[u][i]!=par[v][i]){
                u=par[u][i];
                v=par[v][i];
            }
        }
        return par[u][0];
    };
    dfs(0);
    lcaInit();
    vector<int> first(n,-1);
    vector<vector<int>> in(n,vector<int>(3,0)),out(n,vector<int>(3,0));
    // 0...左子树根 1...右子树根 2...父节点
    // in[i,j]...从i节点往j方向走需要带上几个邮包
    // out[i,j]...从j方向回到i节点时可以运达几个邮包
    for(int i=1;i<=m;i++){
        int s,t;
        cin>>s>>t;
        s--,t--;
        int fa=lca(s,t);
        int cs=-1,ct=-1;
        if(fa!=s){
            int now=up(s,dep[s]-dep[fa]-1);
            for(int i=0;i<adj[fa].size();i++){
                if(adj[fa][i]==now){
                    cs=i;
                }
            }
        }
        if(fa!=t){
            int now=up(t,dep[t]-dep[fa]-1);
            for(int i=0;i<adj[fa].size();i++){
                if(adj[fa][i]==now){
                    ct=i;
                }
            }
        }
        if(fa==s){
            in[s][ct]++;
            out[t][2]++;
        }
        else if(fa==t){
            in[s][2]++;
            out[t][cs]++;
        }
        else{
            in[s][2]++;
            out[t][2]++;
            if(first[fa]==ct){
                cout<<0<<endl;
                return 0;
            }
            first[fa]=cs;
        }
    }
    vector dp(n,vector<pair<int,int>>(k+1,{-1,0}));
    vector vis(n,vector<int>(k+1,0));
    auto work=[&](auto& work,int u,int j){
        if(vis[u][j]){
            return dp[u][j];
        }
        vis[u][j]=1;
        vector<int> ord(adj[u].size());
        iota(ord.begin(),ord.end(),0);
        do{
            if(first[u]!=-1&&ord[0]!=first[u]&&!ord.empty()){
                continue;
            }
            bool ok=true;
            int nj=j;
            nj-=out[u][2];
            int ans=1;
            for(int c:ord){
                nj+=in[u][c];
                if(nj>k){
                    ok=false;
                    break;
                }
                int q;
                tie(nj,q)=work(work,adj[u][c],nj);
                if(nj==-1){
                    ok=false;
                    break;
                }
                ans=(1ll*ans*q)%P;
                nj+=in[adj[u][c]][2];
                if(nj>k){
                    ok=false;
                    break;
                }
                nj-=out[u][c];
            }
            if(!ok) continue;
            dp[u][j].second=(dp[u][j].second+ans)%P;
            dp[u][j].first=nj;
        }while(next_permutation(ord.begin(),ord.end())); 
        //枚举所有可能的走的顺序
        return dp[u][j];
    };
    cout<<work(work,0,0).second<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值