树上/Dag上移动端点的区间动态规划
首先引入例题 。
给定一棵包含 \(n(n\le 2000)\) 个点的树,令树上 \(i\) 号点的点权为 \(P_i\)。定义函数 \(MEX(u, v)\) 如下:
- 令 \(S\) 为树上 \(u\) 到 \(v\) 路径上所有点的点权的集合(包含 \(P_u\) 和 \(P_v\));
- 随后计算出集合 \(S\) 的 MEX(最小未出现非负整数)。
定义一棵树的权值为:
\[\sum_{u=1}^{n} \sum_{v=1}^{n} MEX(u, v) \]现在,你需要为树上的每个点确定一个 \([0, n-1]\) 范围内的点权,且需要保证点权两两不同。求出分配点权后树的最大可能权值。
容易发现 ,必然存在一条链满足长度等于 \(Mex\) , 这是因为移走某个值一定不优 。
并且可以用 \(Mex\) 的经典结论均分贡献 ,跨过 \(0\) 的所有点对对答案贡献累加 \(1,\) 跨过 \(0-1\) 的所有点对再对答案累加 \(1\) , 以此类推 。
考虑设计 \(F[i][j]\) 为 \(i,j\) 为端点的最大值 。树上的链只会被长度较小的链更新两次,一共有 \(n^2\) 条链 ,故时间复杂度为 \(O(n^2)\)
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a,rep##i=b;i<=rep##i;i++)
#define per(i,a,b) for(int i=a,rep##i=b;i>=rep##i;i--)
#define int long long
#define lowbit(x) (x&-x)
#define fi first
#define se second
#define all(x) x.begin(),x.end()
#define sz(x) (static_cast<int>(x.size()))
#define pii std::pair<int,int>
#define Debug(x) ErrPut(#x,':',x,'\n');
#define Cont(...) Self(Self,__VA_ARGS__)
#define Fuck(...) ErrPut("Fuck ",__VA_ARGS__,'\n');
template<typename ...T>void InPut(T&...x){((std::cin>>x),...);}
template<typename ...T>void OutPut(const T&...x){((std::cout<<x),...);}
template<typename ...T>void ErrPut(const T&...x){((std::cerr<<x),...);}
using std::set;
using std::multiset;
using std::vector;
void Main(int Case){
int n;InPut(n);
vector<vector<int>>To(n+1);
rep(i,2,n){
int x,y;InPut(x,y);
To[x].push_back(y);
To[y].push_back(x);
}
vector<vector<int>>F(n+1,vector<int>(n+1));
vector<std::pair<pii,pii>>Kpl,Nex;
vector<int>Siz(n+1),Dad(n+1);
auto dfs=[&](auto Self,int from,int dad)->void {
Siz[from]=1;
Dad[from]=dad;
for(auto to:To[from]){
if(to==dad)continue;
Cont(to,from);
Siz[from]+=Siz[to];
}
};
dfs(dfs,1,0);
rep(i,1,n){
int res=0,sum=0;
for(auto t:To[i]){
int siz;
if(t==Dad[i])siz=n-Siz[i];
else siz=Siz[t];
res+=sum*siz,sum+=siz;
}
for(auto to:To[i]){
int siz;
if(to==Dad[i])siz=n-Siz[i];
else siz=Siz[to];
if(F[i][to]==0)
F[i][to]=F[to][i]=res+siz*(n-siz);
else
F[i][to]=F[to][i]=std::max(F[i][to],res+siz*(n-siz));
Kpl.push_back({{i,to},{to,i}});
}
}
int ans=0;
while(Kpl.size()){
while(!Kpl.empty()){
int x=Kpl.back().first.first;
int y=Kpl.back().first.second;
int l=Kpl.back().second.first;
int r=Kpl.back().second.second;
Kpl.pop_back();
ans=std::max(ans,F[x][y]);
for(auto to:To[x]){
if(to==l)continue;
if(F[to][y]==0||F[y][to]==0){
Nex.push_back({{to,y},{x,r}});
Nex.push_back({{y,to},{r,x}});
}
int tmp1;
if(to==Dad[x])tmp1=n-Siz[x];
else tmp1=Siz[to];
int tmp2;
if(r==Dad[y])tmp2=Siz[y];
else tmp2=n-Siz[r];
F[y][to]=F[to][y]=std::max(F[to][y],tmp1*tmp2+F[x][y]);
}
}
Nex.swap(Kpl);
}
OutPut(ans+n,'\n');
}
int32_t main(){
#ifdef LOCAL
freopen("In.txt","r",stdin);
freopen("Out.txt","w",stdout);
#endif
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int Task=1;InPut(Task);
for(int Case=1;Case<=Task;Case++){
Main(Case);
}
return 0;
}
设计 f[i][j] 作为从 i 出发到 j 能搬运的数量 , 又因为编号小移到编号大 , 直接区间 dp
f[i][j]=sum(std::min(f[i][w],f[w][j]))

浙公网安备 33010602011771号