首先把用tajan把橋邊全部找出來,橋邊會把圖分成若干個雙聯通分量。
把每個雙聯通分量並成乙個點,橋邊作為邊,這會構成一棵樹。
顯然,對於每個詢問加k條邊最多能去掉多少條橋邊,就是用k條簡單路徑去覆蓋這棵樹,最多能覆蓋多少條邊。
有乙個很優的貪心,把k按1-q做,每次找到樹的直徑,答案加上直徑的長度,把直徑壓成乙個點,繼續做k+1的。
但是這個貪心顯然有反例:
如上圖:
按貪心,第一次我們會選擇c->d,第二次選擇a->b,第三次選擇e->f。
但是,明顯第一次選擇a->e->f->d,第二次選擇b->e->f->c,會使得k=2時答案最優。
於是集訓隊大爺出題人的「標程」是偽的。
大爺大概給了個dfs序的偽做法,建棵線段樹,區間[l..r]表示dfs序為[l..r]的點所構成的子樹的直徑,根據直徑的乙個性質,兩顆子樹合併後的新的樹直徑的兩個端點一定是原來兩顆子樹的四個直徑端點的兩個,所以用o(log n)求lca,這是o(n log n log n)的.
對於每次把直徑壓成乙個點,其實不用這麼想,想象把u->fa[u]這條邊刪掉.就相當於把u的子樹的深度減1,顯然它的子樹的直徑不會變,打個lazy_tag就好了。
講了這麼多,都是偽的。
來講講鎮中大佬的做法:
假設我第一次選了直徑c->d,第二次選了a->b,實際上這是可以合併的,就可以交叉合併成a->e->f->d,b->e->f->c。
所以我可以在第一次搞出直徑的基礎上,再選兩條從直徑出發的最長的互不相交的長鏈,每次都是如此。
所以以直徑的端點為根,進行長鏈剖分,再將鏈按長度排序,依次選長的就好了。
長鏈剖分,和樹鏈剖分類似,樹鏈剖分重兒子的關鍵字是子樹大小,改為子樹的最深度即可。
**較繁瑣。
code:
#include
#include
#include
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
const int n = 100005;
int n, m, q, x, y, sumq, ans;
int final[n], tot;
struct edge e[n * 4], c[n * 4];
int dfn[n], low[n], bz[n * 4], bd[n * 4], tt;
int f[n];
int fi[n], tp;
void link(int
x, int
y) void init()
}void dg(int
x, int
last) else
}}int find(int
x) void bin(int
x, int
y) void tlink(int
x, int
y) void build()
tp = 1;
fo(i, 2, tot) if((i & 1) && bz[i])
}int d[n], dis[n], last[n], bx[n], tbx;
int bfs(int
x) }
return d[d[0]];
}int by[n], dep[n], sd[n], son[n], top[n];
void dg1(int
x) by[x] = 0;
}void dg2(int
x) }
bool rank(int
x, int
y) int main()
}
清華集訓2017模擬12 09 塔
有乙個塔,他的名字叫做粽粑,粽粑的每一層都有乙個顏色 粽粑非常厲害,它在吸收天地精華之後會長高.粽粑的長高方式有兩種 1.在塔頂長出一層.2.在塔底長出一層,即原來的第一層變成第二層,第二層變成第三層,以此類推,新長出來的是第一層.粽粑有可能在某個時刻不是很開心,這個時候它會撤銷它的前若干次長高.你...
清華集訓2017模擬12 09 塔
有乙個塔,他的名字叫做粽粑,粽粑的每一層都有乙個顏色 粽粑非常厲害,它在吸收天地精華之後會長高.粽粑的長高方式有兩種 1.在塔頂長出一層.2.在塔底長出一層,即原來的第一層變成第二層,第二層變成第三層,以此類推,新長出來的是第一層.粽粑有可能在某個時刻不是很開心,這個時候它會撤銷它的前若干次長高.你...
清華集訓2017模擬12 10 大佬的難題
1 n 2e6,時限 2.5s 容斥原理瞎搞。設a,b,c分別為滿足三個條件的集合。根據容斥原理,有 a b c a b c a b a c b c a b c 而a b c同時會等同於總數n n 1 都大於的個數 即都小於的個數a b c 所以 n n 1 a b c a b c a b a c ...