图论
P4281 [AHOI2008] 紧急集合 / 聚会
当点 \(x\) 在 \(y,z\) 上时,答案即为 \(x\)。
不然情况如下图:
::: fold
x
|
|
p
/ \
y z
:::
答案为 \(p\)。
所以答案为三个点两两的 \(LCA\) 中,深度较大的 \(LCA\)。
距离预处理树上差分。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
vector<int>v[500005];
int d[500005],fa[500005][20];
void dfs(int x,int f){
fa[x][0]=f;
for(int i=0;i<v[x].size();i++){
if(v[x][i]==f) continue;
d[v[x][i]]=d[x]+1;
dfs(v[x][i],x);
}
}
void init(){
for(int k=1;k<=19;k++){
for(int i=1;i<=n;i++){
if(fa[i][k-1]!=0&&fa[fa[i][k-1]][k-1]!=0){
fa[i][k]=fa[fa[i][k-1]][k-1];
}
}
}
}
int lca(int x,int y){
if(d[x]<d[y]) swap(x,y);
for(int i=19;i>=0;i--){
if(d[fa[x][i]]>=d[y]) x=fa[x][i];
}
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
}
if(x==y) return x;
return fa[x][0];
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n-1;i++){
int u,vv;
cin>>u>>vv;
v[u].push_back(vv);
v[vv].push_back(u);
}
d[1]=1;
dfs(1,0);
init();
while(m--){
int x,y,z;
cin>>x>>y>>z;
int p=0;
if(d[lca(x,y)]>=d[p]) p=lca(x,y);
if(d[lca(x,z)]>=d[p]) p=lca(x,z);
if(d[lca(y,z)]>=d[p]) p=lca(y,z);
int ans=0;
ans+=d[x]+d[p]-2*d[lca(x,p)];
ans+=d[y]+d[p]-2*d[lca(y,p)];
ans+=d[z]+d[p]-2*d[lca(z,p)];
cout<<p<<" "<<ans<<endl;
}
return 0;
}
P2971 [USACO10HOL] Cow Politics G
考虑分党派处理。
对于某个党派,先取其中前两个节点 \(a,b\),把它们作为当前点集直径的两个端点。
之后依次加入新的节点 \(x\)。
假设加入 \(x\) 之前,当前所有节点的直径端点为 \(a,b\),那么加入 \(x\) 后,新的直径只可能是以下三条路径中的最长者:
因为原来的所有节点之间的最大距离已经是 \(dis(a,b)\),加入一个新点以后,如果直径发生改变,那么新的直径一定包含新加入的节点 \(x\)。
这样不断加入该党派的所有节点,最后得到的 \(a,b\) 就是这个党派的直径端点,答案就是 \(dis(a,b)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
vector<int>v[200005];
int d[200005],fa[200005][20];
int h[200005];
vector<int>g[200005];
void dfs(int x,int f){
fa[x][0]=f;
for(int i=0;i<v[x].size();i++){
if(v[x][i]==f) continue;
d[v[x][i]]=d[x]+1;
dfs(v[x][i],x);
}
}
void init(){
for(int k=1;k<=19;k++){
for(int i=1;i<=n;i++){
if(fa[i][k-1]!=0&&fa[fa[i][k-1]][k-1]!=0){
fa[i][k]=fa[fa[i][k-1]][k-1];
}
}
}
}
int lca(int x,int y){
if(d[x]<d[y]) swap(x,y);
for(int i=19;i>=0;i--){
if(d[fa[x][i]]>=d[y]) x=fa[x][i];
}
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
}
if(x==y) return x;
return fa[x][0];
}
signed main(){
cin>>n>>k;
int gg=0;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
if(y==0) gg=i;
h[i]=x;
g[x].push_back(i);
v[y].push_back(i);
v[i].push_back(y);
}
d[gg]=1;
dfs(gg,0);
init();
for(int i=1;i<=k;i++){
int a=g[i][0],b=g[i][1];
for(int j=2;j<g[i].size();j++){
int f1=d[g[i][j]]+d[a]-2*d[lca(g[i][j],a)],f2=d[g[i][j]]+d[b]-2*d[lca(g[i][j],b)];
if(max({f1,f2,d[a]+d[b]-2*d[lca(a,b)]})==f1) b=g[i][j];
else if(max({f1,f2,d[a]+d[b]-2*d[lca(a,b)]})==f2) a=g[i][j];
}
cout<<d[a]+d[b]-2*d[lca(a,b)]<<endl;
}
return 0;
}
P6190 [NOI Online #1 入门组] 魔法
先把路径问题转化成 \(\min,+\) 矩阵乘法。定义矩阵乘法为
这样矩阵乘法就表示“把两段路径拼起来”。
设 \(I\) 为单位矩阵:
再构造两个矩阵:
- \(I_2\):普通道路矩阵,其中存在边 \(u\to v\)、权值为 \(w\) 时,\(I_{2,u,v}=w\),并且 \(I_{2,i,i}=0\);
- \(I_3\):魔法矩阵,其中存在边 \(u\to v\)、权值为 \(w\) 时,\(I_{3,u,v}=-w\),同时 \(I_{3,i,i}=0\)。
这里 \(I_3\) 的对角线为 \(0\),相当于:
即这一轮可以选择使用一次魔法,也可以选择不使用魔法。
由于普通边权全部为正,普通最短路一定可以取成简单路径,因此最多经过 \(n-1\) 条边。于是从单位矩阵开始连续乘 \(n-1\) 次 \(I_2\):
就能得到普通道路的全源最短路矩阵 \(D\)。
接下来,一轮“普通走任意距离,然后可以使用一次魔法”的转移矩阵为
也就是
如果进行 \(k\) 轮,得到
由于 \(\min,+\) 矩阵乘法满足结合律,所以 \(P^k\) 可以使用矩阵快速幂,在 \(O(\log k)\) 次矩阵乘法内求出。
最后一次魔法之后,还可能继续沿普通道路走到终点,所以最后还需要乘一次普通最短路矩阵 \(D\)。
因此最终要求的矩阵为
答案就是
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,kk;
int l[105][105],l2[105][105],l3[105][105],p[105][105];
int ll[105][105];
void qpow(int a){
while(a){
if(a&1){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) ll[i][j]=1e18;
else ll[i][j]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(l[i][k]==1e18||p[k][j]==1e18) continue;
ll[i][j]=min(ll[i][j],l[i][k]+p[k][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
l[i][j]=ll[i][j];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) ll[i][j]=1e18;
else ll[i][j]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(p[i][k]==1e18||p[k][j]==1e18) continue;
ll[i][j]=min(ll[i][j],p[i][k]+p[k][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
p[i][j]=ll[i][j];
}
}
a>>=1;
}
}
signed main(){
cin>>n>>m>>kk;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) l2[i][j]=1e18;
if(i!=j) l3[i][j]=1e18;
}
}
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
l2[u][v]=w;
l3[u][v]=-w;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) l[i][j]=1e18,p[i][j]=1e18;
}
}
for(int p1=1;p1<=n-1;p1++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) ll[i][j]=1e18;
else ll[i][j]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(p[i][k]==1e18||l2[k][j]==1e18) continue;
ll[i][j]=min(ll[i][j],p[i][k]+l2[k][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
p[i][j]=ll[i][j];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) ll[i][j]=1e18;
else ll[i][j]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(p[i][k]==1e18||l3[k][j]==1e18) continue;
ll[i][j]=min(ll[i][j],p[i][k]+l3[k][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
p[i][j]=ll[i][j];
}
}
qpow(kk);
for(int p1=1;p1<=n-1;p1++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) ll[i][j]=1e18;
else ll[i][j]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(l[i][k]==1e18||l2[k][j]==1e18) continue;
ll[i][j]=min(ll[i][j],l[i][k]+l2[k][j]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
l[i][j]=ll[i][j];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
l[i][j]=ll[i][j];
}
}
cout<<l[1][n];
return 0;
}
P1266 [BalticOI 2002] 速度限制
把当前位置和当前速度一起作为最短路状态。
因为遇到未知限速的道路时,行驶时间取决于之前保留的速度,所以不能只记录到某个点的最短时间。
转移时:
- 若道路限速未知,则保持原速度;
- 若道路有明确限速,则当前速度变为该限速。
在这些状态上跑 Dijkstra。每次某个状态被成功松弛时,记录它是从哪个前驱节点和前驱速度转移过来的。
最后枚举所有到达终点的速度状态,选出总时间最短的那个状态,再沿记录的前驱状态一路倒推回初始状态 \((0,70)\),即可还原出整条最优路径。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,d;
struct edge{
int vv,vt;
double l;
};
vector<edge>v[155];
bool operator < (edge x,edge y){
return x.l>y.l;
}
priority_queue<edge>q;
double dp[155][505];
bool vis[155][505];
struct las{
int vv,vt;
};
las lasp[155][505];
void dijkstra(){
for(int i=0;i<=n-1;i++){
for(int j=0;j<=500;j++){
dp[i][j]=1e18;
}
}
dp[0][70]=0;
q.push({0,70,0});
while(!q.empty()){
int p=q.top().vv;
int vt=q.top().vt;
q.pop();
if(vis[p][vt]) continue;
vis[p][vt]=1;
for(int i=0;i<v[p].size();i++){
if(v[p][i].vt==0){
if(dp[v[p][i].vv][vt]>dp[p][vt]+(v[p][i].l*1.0/vt)){
dp[v[p][i].vv][vt]=dp[p][vt]+(v[p][i].l*1.0/vt);
lasp[v[p][i].vv][vt]={p,vt};
q.push({v[p][i].vv,vt,dp[v[p][i].vv][vt]});
}
}
else{
int vp=v[p][i].vt;
if(dp[v[p][i].vv][vp]>dp[p][vt]+(v[p][i].l*1.0/vp)){
dp[v[p][i].vv][vp]=dp[p][vt]+(v[p][i].l*1.0/vp);
lasp[v[p][i].vv][vp]={p,vt};
q.push({v[p][i].vv,vp,dp[v[p][i].vv][vp]});
}
}
}
}
}
signed main(){
cin>>n>>m>>d;
for(int i=1;i<=m;i++){
int u,vv,vt,l;
cin>>u>>vv>>vt>>l;
v[u].push_back({vv,vt,l});
}
dijkstra();
double mh=1e18;
int r1=d,r2=-1;
for(int i=0;i<=500;i++){
if(dp[d][i]<mh) mh=dp[d][i],r2=i;
}
stack<int>st;
while(r1!=0){
st.push(r1);
int p1=lasp[r1][r2].vv,p2=lasp[r1][r2].vt;
r1=p1,r2=p2;
}
st.push(0);
while(!st.empty()) cout<<st.top()<<" ",st.pop();
return 0;
}
OI / C++ / Algorithm
懒惰者出局。

浙公网安备 33010602011771号