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′=j−n,然后我们枚举所有可行的走的顺序(先走右,先走左),根据该顺序来递归搜索更新答案。要用记忆化搜索来保证复杂度为 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;
}