题解:P17364 [ECNA 2024] Pony-less Express
lailai0916 · · 题解
题意简述
以首都
农庄
解题思路
一个节点能够决定的是子节点的通知顺序。不同子树使用各自的马匹,收到消息后的工作相互独立。因此,固定节点收到消息的日期后,仅需把不同子树分配到连续的日期上。
设
定义
直接枚举排列不可行,但每个子节点与每个通知次序之间的代价已经确定。建立一个二分图:左侧为
这个对应是双向的。任意合法通知顺序都让每个子节点和每个次序各使用一次,因而形成完美匹配。反过来,匹配可以直接排列成连续的通知次序,各子树再执行自身最优方案,不会争用其他子树的马匹。因此,匹配没有放宽限制,也没有遗漏最优方案。
实现中按子树从下往上计算,矩阵 a 保存当前二分图的边权,match 用匈牙利算法求最小权完美匹配。p[j] 保存右侧位置 x 和 y 保存两侧势能;dis 维护增广过程中尚未加入交错树的右侧节点的最小剩余边权。每次调整势能使至少一条候选边的剩余权变为 pre 记录的路径增广。这里只需要标准的最小权匹配,子树代价不要求单调或满足额外性质。叶节点对应空匹配,贡献为
日期范围也可以在向下遍历时确定。设节点
任意一条根到节点的路径上,各父节点的子节点集合互不相交。通知次序至多为对应父节点的子节点数,所以路径上所有次序之和不超过 dfs(u,l,r) 先计算所有子节点,再填充该范围内的
设节点 int。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=205;
const int inf=0x3f3f3f3f;
int c[N],d[N],f[N][N],a[N][N],x[N],y[N],p[N],pre[N],dis[N];
bool vis[N];
vector<int> G[N];
int match(int n)
{
fill(x,x+n+1,0);
fill(y,y+n+1,0);
fill(p,p+n+1,0);
for(int i=1;i<=n;i++)
{
p[0]=i;
fill(dis,dis+n+1,inf);
fill(vis,vis+n+1,0);
int u=0;
do
{
vis[u]=1;
int v=0,mn=inf;
for(int j=1;j<=n;j++)
{
if(vis[j])continue;
int val=a[p[u]][j]-x[p[u]]-y[j];
if(val<dis[j]){dis[j]=val;pre[j]=u;}
if(dis[j]<mn){mn=dis[j];v=j;}
}
for(int j=0;j<=n;j++)
{
if(vis[j]){x[p[j]]+=mn;y[j]-=mn;}
else dis[j]-=mn;
}
u=v;
}while(p[u]);
while(u)
{
p[u]=p[pre[u]];
u=pre[u];
}
}
return -y[0];
}
void dfs(int u,int l,int r)
{
int m=G[u].size();
for(auto v:G[u])dfs(v,l+1,r+m);
for(int i=l;i<=r;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=1;k<=m;k++)a[j][k]=f[G[u][j-1]][i+k];
}
f[u][i]=c[u]*(d[u]-i)*(d[u]-i)+match(m);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int r;
cin>>c[i]>>d[i]>>r;
G[r].push_back(i);
}
dfs(0,0,0);
cout<<f[0][0]<<'\n';
return 0;
}