2022CCPC绵阳-E-一类图上DP优化
link:https://codeforces.com/gym/104065/problem/E
题意:\(n\) 个城市由 \(m\) 条边连成一张无向图,每个点所属连通块大小至少是2。接下来有 \(q\) 次危机,每次危机恰发生在一个城市 \(x\),需要所有在 \(x\) 城市的所有居民迁移到其他城市(之后可以迁移回来)。第 \(i\) 个城市有 \(a_i\) 人,每个人经过一条边需要花费 \(w(u,v)\) 的代价。已知接下来发生危机的城市,问所有危机发生后,所有人的总代价最小是多少?
\(1\leq n,m,q\leq 10^5\)
首先对每个人单独考虑,设 \(f_i\) 是初始在 \(i\) 号点的人经过所有危机需要的花费,则 \(ans=\sum f_i a_i\)
然后很明显危机发生在 \(x\) 处,\(x\) 的人只需要考虑迁移到某相邻结点\(y\) ,则花费为 \(w(x,y)+\) 剩下时间里 \(y\) 需要的花费,所以很明显 \(f\) 需要倒着计算。设 \(f(i,j)\) 表示\(i\) 号点在 \(j\) 时刻往后考虑需要的最小花费,则
\[f(i,j)=\left\{\begin{aligned}&f(i,j+1),b_j\neq i\\ &\min_{k\in N(i)}f(k,j+1)+w(i,k),b_j=i\end{aligned}\right.
\]
乍一看是个 \(O(nq)\) 的DP,每个点从相邻结点转移过来。从这题也可以看出这类问题的通用解法:按度数分块
- 对 \(deg(x)\leq T\) 的点,暴力枚举邻域的点
- 对\(deg(x)>T\) 的点,这样的点不超过 \(2m/T\) 个。对于这些点是千万不能暴力枚举邻域的,我们可以对每个大度点开一个
multiset
,在计算每个小度点时,暴力更新大度点。 - 缕清一下需要写的东西:
- 小度点连着任何点都可以直接暴力更新
- 大度点连着小度点,需要在小度点的
multiset
里直接获取最小值 - 大度点连着大度点呢?因为不超过 \(2m/T\) 个,所以在建图的时候可以直接按度数排序,暴力枚举最大的几个点,暴力计算
- 这样的复杂度:
- 问到小度点的时候,\(O(T)\) 地求值,\(O(T\log n)\) 地更新大度点
- 问到大度点的时候,\(O(\log n+2m/T)\) 地求值
- 均摊复杂度\(O(\log n+T\log n+\frac{2m}{T})\),前面 \(O(\log n)\) 不动,后面 \(T=\sqrt{2m\log n}\) 时取得最小
- 最终复杂度是 \(O(q\log n+q\sqrt{2m\log n})=O(q\sqrt{2m\log n})\)
- 这里会发现,我们复杂度的优越性其实来自于两者之中,有一个带 \(\log\) 而另一个可以完全不带,如果处理两种点都有 \(\log n\) 的话其实会退化成 \(O(q\sqrt m \log n)\) ,这样对于 \(10^5\) 的数据基本是跑不过的(除非常数特别小)
- 这题还可以对时间分治,去掉根号里的\(\log n\),不过那个方法可能没什么普适性
#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+5;
const int MOD=998244353;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,q,T,head[N],deg[N],b[N];
vector<vector<pii>> G;
ll a[N],f[N];
multiset<ll> S[N];
int main(){
fastio;
cin>>n>>m>>q;
G=vector<vector<pii>>(n+1);
T=sqrt(2*m*log(n+1));
rep(i,1,n)cin>>a[i];
rep(i,1,m){
int u,v,w;cin>>u>>v>>w;
deg[u]++;G[u].pb(mp(v,w));
deg[v]++;G[v].pb(mp(u,w));
}
rep(i,1,q)cin>>b[i];
rep(i,1,n)sort(G[i].begin(),G[i].end(),[](pii p1,pii p2){return deg[p1.first]>deg[p2.first];});
rep(x,1,n)if(deg[x]>T)for(auto [v,w]:G[x])if(deg[v]<=T)S[x].insert(w);
for(int tc=q;tc>=1;tc--){
int x=b[tc];
if(deg[x]<=T){
for(auto [v,w]:G[x])if(deg[v]>T)S[v].erase(S[v].find(f[x]+w));
f[x]=INF;
for(auto [v,w]:G[x])f[x]=min(f[x],f[v]+w);
for(auto [v,w]:G[x])if(deg[v]>T)S[v].insert(f[x]+w);
}else{
f[x]=*S[x].begin();
for(auto [v,w]:G[x]){
if(deg[v]<=T)break;
f[x]=min(f[x],f[v]+w);
}
}
}
ll ans=0;
rep(i,1,n)ans=(ans+f[i]%MOD*a[i]%MOD)%MOD;
cout<<ans;
return 0;
}