運輸計劃
運輸計劃
首先熟悉一下題目:
在一棵有邊權的樹(n個節點)上有m條路徑,清零一條邊的邊權使得m條路徑的最大值最小
至於資料範圍
20分
m=1
好啦,好像可做,一眼望去全是水
只需求出一條鏈上的所有邊並計算邊權和及最大邊權(暴力往上跳並記錄即可)
邊權和減去最大邊權即為答案
那麼我們就可以O(n)過掉這道題了(不嫌麻煩的話也可以O(log n)搞樹上路徑)
60分?
從未如此接近滿分
全是鏈,這意味著什麼(並不意味著什麼)
想了想,發現好像60分並不好搞
考慮一下暴力吧
超級暴力:暴力列舉刪每一條邊,統計刪完這條邊之後最長鏈的長度,取最小值就是答案,複雜度O(n^2 m),25分
剛才的小優化
考慮優化暴力
列舉刪哪條邊O(n)顯然已經達到理論下限
如果非要搞它的話只能排除那些不被經過的邊,效率高不了多少
接下來是統計每條鏈的長度
全是鏈哎,求線性區間和,字首和優化,消去一個O(n)
那個O(m)好像沒有什麼有效的優化
這樣,複雜度降至O(nm),40分
然後其他資料,搞樹鏈剖分動態修改、查詢可以多拿一些分,複雜度O(nm log n),60分
怎麼辦
QAQ,60分都拿不到了嗎
可不可以不實際改邊權呢?
經過不會就猜二分
經過深入思考,我們發現:
最短時間為t,前提是對於length>t的所有鏈,總能找到至少一條長為k公共邊,使得最長鏈的長度max length-k<=t
如果知道答案,好像不僅不用列舉最長鏈,還可以把列舉刪邊變為貪心刪掉被全部滿足條件的鏈經過的最長邊,穩賺一個O(n)和一個O(m)
考慮二分答案
如果能夠在時間t1內完成任務,那麼對於t2>t1,總能在時間t2內完成任務
所以答案符合單調性
可以二分答案
Check函式怎麼寫呢,看一看能不能找到找到至少一條長為k公共邊,使得最長鏈的長度max length-k<=t
設length>t的邊的個數為number
我們必須知道一條邊是否曾被number個鏈同時經過,唯一的方法好像就是差分了,check函式可以寫成O(n + m)的,總複雜度O((n + m)log n),60分
100分
二分答案的做法放到樹上呢
考慮線性資料上二分的完整做法
預處理每一條鏈的length,二分答案,放到check函式裡搞
沒問題
LCA求出每條鏈的length,還是二分,check函式換成樹上差分
最後發現正解只要一句話:
求鏈長+二分
存圖
存樹
struct edge{
int v,nxt,w;
}e[maxx<<1],q[maxx<<1];
inline void add_(int u,int v,int w){
e[++js].v = v;
e[js].w = w;
e[js].nxt = head[u];
head[u] = js;
}
存每一條路徑(方便樹上差分)
struct length{
int len,lca,u,v;//儲存每一條路徑的長度,lca,起點和終點
}len[maxx];
inline void addque(int u,int v){//對所求路徑建圖
q[++js].v = v;
q[js].nxt = headt[u];
headt[u] = js;
}
並查集
inline int find(int x){
if(f[x]==x) return x;
else
return f[x] = find(f[x]);
}
tarjan求每條鏈的長度,lca,以及最長鏈的len
void tarjan(int u,int pre){//pre前驅,防止走到自己
for (int i = head[u];i;i = e[i].nxt){
int v = e[i].v;
if(v == pre)//如果下一個點是自己的前驅就跳過
continue;
dis[v] = dis[u] + e[i].w;//存下每個點到原點的距離
tarjan(v,u);
a[v] = e[i].w;//連到v這個點的上一條邊的權值;
int f1 = find(v);
int f2 = find(u);
if(f1!=f2)
f[f1] = find(f2);//存公共最先
vis[v] = 1;
}
for (int i = headt[u];i;i = q[i].nxt){
if(vis[q[i].v]){
int p = (i + 1)>>1;
len[p].lca = find(q[i].v);//塔尖求lca
len[p].len = dis[u]+dis[q[i].v]-2*dis[len[p].lca];//求鏈的長度(兩點到原點距離-兩點lca的dis*2)
maxlen = max(maxlen,len[p].len);//存下最長鏈,二分答案的時候用
}
}
樹上差分
inline bool check(int x){
memset(s,0,sizeof(s));
num = ret = 0;//ret代表多條路徑重複部分最長的邊
for(int i = 1; i <= m; i++)
if(len[i].len>x){//樹上差分
s[len[i].u]++;
s[len[i].v]++;
s[len[i].lca]-= 2;
num++;//記錄len>x的鏈的個數
}
dfs(1,0);
if(maxlen-ret>x)
return 0;
return 1;
}
void dfs(int u,int pre){
for(int i = head[u];i;i = e[i].nxt){
int v = e[i].v;
if(v == pre)
continue;
dfs(v,u);
s[u]+=s[v];
}
if(s[u]==num&&a[u]>ret)
ret = a[u];
}
node
/*
work by:Ariel
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxx = 3e5 + 10;
struct edge{
int v,nxt,w;
}e[maxx<<1],q[maxx<<1];
struct length{
int len,lca,u,v;//儲存每一條路徑的長度,lca,起點和終點
}len[maxx];
int js,head[maxx],headt[maxx];//存圖所用的變數
int dis[maxx];//記錄路徑
int f[maxx];//並查集
int a[maxx];//這個點前一條邊的權值
int vis[maxx],maxlen;
int s[maxx];
int num,ret;
int n,m,ans;
inline void add_(int u,int v,int w){//建圖
e[++js].v = v;
e[js].w = w;
e[js].nxt = head[u];
head[u] = js;
}
inline void addque(int u,int v){//對所求路徑建圖
q[++js].v = v;
q[js].nxt = headt[u];
headt[u] = js;
}
inline int find(int x){
if(f[x]==x) return x;
else
return f[x] = find(f[x]);
}
void tarjan(int u,int pre){//pre前驅,防止走到自己
for (int i = head[u];i;i = e[i].nxt){
int v = e[i].v;
if(v == pre)//如果下一個點是自己的前驅就跳過
continue;
dis[v] = dis[u] + e[i].w;//存下每個點到原點的距離
tarjan(v,u);
a[v] = e[i].w;//連到v這個點的上一條邊的權值;
int f1 = find(v);
int f2 = find(u);
if(f1!=f2)
f[f1] = find(f2);//存公共最先
vis[v] = 1;
}
for (int i = headt[u];i;i = q[i].nxt){
if(vis[q[i].v]){
int p = (i + 1)>>1;
len[p].lca = find(q[i].v);//塔尖求lca
len[p].len = dis[u]+dis[q[i].v]-2*dis[len[p].lca];//求鏈的長度(兩點到原點距離-兩點lca的dis*2)
maxlen = max(maxlen,len[p].len);//存下最長鏈,二分答案的時候用
}
}
}
void dfs(int u,int pre){
for(int i = head[u];i;i = e[i].nxt){
int v = e[i].v;
if(v == pre)
continue;
dfs(v,u);
s[u]+=s[v];
}
if(s[u]==num&&a[u]>ret)
ret = a[u];
}
inline bool check(int x){
memset(s,0,sizeof(s));
num = ret = 0;//ret代表多條路徑重複部分最長的邊
for(int i = 1; i <= m; i++)
if(len[i].len>x){//樹上差分
s[len[i].u]++;
s[len[i].v]++;
s[len[i].lca]-= 2;
num++;//記錄len>x的鏈的個數
}
dfs(1,0);
if(maxlen-ret>x)
return 0;
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1;i < n; i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_(u,v,w);
add_(v,u,w);
}
for (int i = 1; i <= n; i++)
f[i] = i;//把每一個點的父親設為自己(並查集初始化)
js = 0;
for (int i = 1 ;i <= m; i++){
int x,y;
scanf("%d%d",&x,&y);
len[i].u = x;
len[i].v = y;
addque(x,y);
addque(y,x);
}
tarjan(1,0);
int l = 0,r = maxlen;
while(l <= r){
int mid = (l+r)>>1;//二分
if(check(mid)){
r = mid - 1;
ans = mid;
}
else l = mid + 1;
}
printf("%d",ans);
return 0;
}