题解 AT4144 【[ARC098D] Donation】

command_block

2021-01-12 18:54:56

Solution

**题意** : 给出一张 $n$ 个点 $m$ 条边的无向连通图。 点 $u$ 有两参数 $A_u,B_u$。当你经过该点时,必须至少拥有 $A_u$ 元,在该点打卡需要花费 $B_u$ 元。 你可以从任意点开始旅程。你需要在所有点打卡一次,问初始时最少需要携带多少元。 $n\leq 10^5$ ,时限$\texttt{2s}$。 ------------ 很有意思的一个题。 - **性质** : 在某点打卡后必不会再次访问某点。 **解释** :不难发现,旅行中的钱数是单调不增的。 若两次访问某点,第一次不打卡,第二次打卡要比第一次就打卡优,因为中间的一段路程余钱更多。 若遵守上述策略,令 $C_u=\max(A_u-B_u,0)$ ,约束可以等价于 : 在 $u$ 点的所有时刻钱数都 $\geq C_u$。 一种直观的想法是 : 按照 $C$ 排序,并按顺序打卡。 可惜的是,在点之间移动时,受到图的限制,途中可能经过 $C$ 更大的节点。 我们将 $u\rightarrow v$ 的边权设为 $\max(C_u,C_v)$ ,求最小生成树,不难发现最优的移动路径只会在最小生成树上,这样就把图简化成了树。 最小生成树的性质仍然不够好(边上的 $C$ 是乱序的),考虑建立重构树。 观察边权的性质,根据 $\rm Kruskal$ ,建树的过程等价于按照 $C$ 从小到大枚举点 $u$ ,然后尽量加入和 $C$ 更小的点 $v$ 连接的边。 我们连边 ($u\rightarrow v$ 目前的最浅祖先)。这样就建立了类似重构树的结构,满足下面两条关键性质 : - 深度越深, $C$ 越小。 - 两点间路径的 $\max C$ 取得最小值。 接下来在树上 $\rm DP$。 设 $dp[u]$ 为处理完 $u$ 子树(不必回到 $u$)所需的初始钱数,$s[u]$ 为子树内 $B$ 的和。 对于叶节点,显然有 $dp[u]=B_u+C_u$。 对于非叶节点,若有 $k$ 个子树,要先处理 $k-1$ 个,然后在 $u$ 打卡,然后处理最后一个。 在处理前面 $k-1$ 个时,由于树中的 $C$ 上大下小,而钱单调不增,所以只需要考虑在 $u$ 打卡的一次约束。 枚举最后进入的子树 $v$ ,则有 $dp[u]=\min\limits_{u\rightarrow v}(s[u]-s[v]+\max(C_u,dp[v]))$ 其中,$s[u]-s[v]$ 是处理前面 $k-1$ 个子树的花费,$\max(C_u,dp[v])$ 是后面的限制。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #define pb push_back #define ll long long #define MaxN 105000 using namespace std; struct UFS{ int f[MaxN]; void Init(int n) {for (int i=1;i<=n;i++)f[i]=i;} int find(int u) {return f[u]==u ? u : f[u]=find(f[u]);} bool merge(int u,int v){ u=find(u);v=find(v); if (u==v)return 0; f[u]=v;return 1; } }F; vector<int> g[MaxN],org[MaxN]; ll dp[MaxN],s[MaxN]; int c[MaxN],b[MaxN]; void pfs(int u) { s[u]=b[u]; for (int i=0;i<g[u].size();i++){ pfs(g[u][i]); s[u]+=s[g[u][i]]; } } void dfs(int u) { if (!g[u].size())dp[u]=b[u]+c[u]; else dp[u]=1ll<<60; for (int i=0,v;i<g[u].size();i++){ dfs(v=g[u][i]); dp[u]=min(dp[u],s[u]-s[v]+max((ll)c[u],dp[v])); } } bool cmp(int A,int B) {return c[A]<c[B];} int n,m,p[MaxN];bool vis[MaxN]; int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++){ scanf("%d%d",&c[i],&b[i]); c[p[i]=i]=max(c[i]-b[i],0); }for (int i=1,u,v;i<=m;i++){ scanf("%d%d",&u,&v); org[u].pb(v);org[v].pb(u); } sort(p+1,p+n+1,cmp); F.Init(n); for (int t=1;t<=n;t++){ int u=p[t]; for (int i=0,v;i<org[u].size();i++) if (vis[v=org[u][i]]){ v=F.find(v); if (F.merge(v,u))g[u].pb(v); } vis[u]=1; } pfs(p[n]);dfs(p[n]); printf("%lld",dp[p[n]]); return 0; } ```