給定一顆邊權為1的樹,點權為v,給定m條從s到t的路徑,對於每個點,求\(ans_i=\sigma_^m [dist(s_j,i)==v[i]]\)
有兩種可能的做法,一種是把路徑全加進去,再每乙個點求\(ans\),另一種是一條一條加路徑,每次求貢獻。而這道題用的是第一種
對於每一條路徑,將\(s->t\)的路徑拆分為\(s->lca->t\),\(s->lca\)為左路徑,\(lca->t\)為右路徑,對於左路徑上的點\(i\),左路徑對它有貢獻當且僅當\(dep[s]-dep[i]==v[i]\),將\(i\)項移動到一邊,就有\(dep[s]==dep[i]+v[i]\),這樣就可以把路徑全部加進去再求\(i\)點的貢獻。右路徑同理有\(dep[s]-2*dep[lca]==v[i]-dep[i]\)
\(dfs\)整顆樹,從下向上回溯時求\(ans\),對於回溯時遇到的點\(i\),加入以它為\(s/t\)的左右路徑,求一次\(ans\),退出時減去以它為\(lca\)的左右路徑即可,然後就發現過不了樣例
(假設當前在以\(u\)為根的子樹中\(dfs\))因為在\(u\)的某一珂子樹\(v\)中的時候,\(u\)的其他子樹裡面的路徑也被記錄下來了,這時就會導致一條路徑對不在它上面的點做出貢獻。
解決方法:因為求的是滿足上面兩個約束的路徑條數,那麼在遞迴進入\(v\)子樹加入裡面的路徑之前,先減去此時滿足約束條件的路徑條數,這樣就保證了做出貢獻的都是\(v\)子樹裡面出發的路徑
另外,如果\(i\)是路徑的\(lca\)的話可能被計算兩次(左右路徑各一次),所以要減1,即當滿足\(v[lca]==dep[s]-dep[lca]\)的時候減1
code:
#include#define n 900005
#define m 300005
using namespace std;
const int temp = 300000;
int n,m;
int dep[m],w[m],fa[m][18],ans[m];
int lsum[n],rsum[n];//桶
vectorl[m],r[m];//lca在上面,出
vectorl[m],r[m];//s,t在下面,入
struct edge
edge[m<<1];int head[m],cnt=1;
void add_edge(int from,int to)
template void read(t &x)
void dfs(int rt)
}int lca(int x,int y)
void dfs(int rt)
//加入rt
for(int i=0;i<(int)l[rt].size();++i)
for(int i=0;i<(int)r[rt].size();++i)
ans[rt]+=lsum[dep[rt]+w[rt]+temp]+rsum[w[rt]-dep[rt]+temp];
//刪除以rt為lca的鏈
for(int i=0;i<(int)l[rt].size();++i)
for(int i=0;i<(int)r[rt].size();++i) }
int main()
dfs(1);
for(int i=1;i<=n;++i) read(w[i]);
for(int i=1;i<=m;++i)
dfs(1);
for(int i=1;i<=n;++i) printf("%d ",ans[i]);
return 0;
}
NOIP2016 天天愛跑步
時間限制 2 s 記憶體限制 512 mb 題目描述 小c同學認為跑步非常有趣,於是決定製作一款叫做 天天愛跑步 的遊戲。天天愛跑步 是乙個養成類遊戲,需要玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一棵包含n個結點和n 1條邊的樹,每條邊連線兩個結點,且任意兩個結點存在一條路徑互相可達。...
NOIP2016天天愛跑步
小c同學認為跑步非常有趣,於是決定製作一款叫做 天天愛跑步 的遊戲。天天愛跑步 是乙個養成類遊戲,需要玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包含 nn n個結點和 n 1n 1n 1條邊的樹,每條邊連線兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從11 1到nn...
NOIP2016 天天愛跑步
看這道題不爽很久了,但一直沒有開它,原因是我不會 我太菜了 看了題解還是寫不來,因為我不會線段樹合併。然後今天學了dsu on tree這種神奇的科技,成功把它a了,效率吊打線段樹合併。於是寫篇題解紀念一下。洛谷p1600 天天愛跑步 不帶修改的樹上路徑資訊的維護,很容易想到樹上差分。我們考慮一條路...