NOI2020 命運(線段樹合併優化dp)

2022-06-24 21:45:09 字數 2649 閱讀 2968

給定一棵\(n\)個點、\(n-1\)條邊的樹,\(1\)號節點為根節點,樹上每條邊都可以取\(0\)或者\(1\),有\(m\)對限制,每對限制\((u,v)\),表示\(u\)到\(v\)的路徑上至少有一條邊的值為\(1\),保證\(u\)為\(v\)的祖先,問有多少中合法的方案對於所有\(m\)個限制都滿足

\(n,m \leq 5 \cdot 10^\)

容易想到容斥,但是只能做出部分分,一般來講,容斥只能做限制數量較少的題

換個思路,樹形\(dp\)

容易發現,對於一些下端點相同的限制,只需要上端點深度最大的限制滿足,那麼這些下端點相同的限制都能滿足

對於一棵子樹,假設兩個端點都在這棵子樹內的限制已經滿足了,但是其他的限制不好做,怎麼辦呢?

只考慮定下了這棵子樹內的邊,其他的邊未定下,初始全取\(0\),那麼對於這棵子樹內邊的取值會直接影響到的限制是乙個端點在子樹內,另乙個端點在子樹外的且這個限制還沒有被滿足的情況,這種限制可以轉換為乙個端點為這棵子樹的根節點,乙個端點在子樹外,在子樹外的端點一定是這棵子樹跟姐點的祖先

要滿足這些限制,由於這些限制的下端點轉換成了同乙個點,所以只需要這棵子樹的根節點和那些限制的上節點中深度最大的點滿足就可以了

我們可以設\(f[x][y]\)表示以\(x\)這根節點的這棵子樹內邊的取值已經定下來,不滿足的限制中上方節點的最大深度為\(y\)的方案數,要滿足限制就是\(x\)到它祖先中深度為\(y\)的節點之間至少存在一條邊的取值為\(1\)

列舉\(dp\)轉移,假設現在在點\(x\),它有乙個兒子為\(y\),根據把他們之間的這條邊的值定義為\(0\)或\(1\)去討論可以推出:

\[f[x][i]=\sum_^f[y][j]\cdot f[x][i]+\sum_^f[y][j]\cdot f[x][i]+\sum_^f[x][j]\cdot f[y][i]

\]再化簡一下,得到:

\[f[x][i]=f[x][i]\cdot (\sum_^f[y][j] + \sum_^f[y][j])+f[y][i]\cdot \sum_^f[x][j]

\]把\(\sum\)用乙個陣列\(sum[x][i]\)代替,記錄字首和,\(sum[x][i]=f[x][1]+f[x][2]+···+f[x][i]\)

那麼原式可以表示為:

\[f[x][i]=f[x][i]\cdot (sum[y][dep[x]]+sum[y][i])+f[y][i]\cdot sum[x][i-1]

\]這樣我們就得到了乙個\(\theta(n^)\)的暴力做法了,能拿到\(36\)分的好成績

由於它的資料有的點\(m\)很小,有的點滿足完全二叉樹,結合多種方法應該能拿到較高的分數

接著考慮優化這個\(dp\),由於它限制的條數只有\(m\)條,所以我們這個方程會有很多沒用的東西,既耗時間又耗記憶體,考慮動態開點,又要將子樹的資訊合併起來,那麼想到線段樹合併,時間複雜度是\(\theta(n\ logn)\)的

#includeusing namespace std;

#define re register int

typedef long long ll;

const int n = 500005;

const int m = 10000005;

const int p = 998244353;

int n, m, cnt, ht, num, rt[n], fa[n], dep[n], g[n], hea[n], nxt[n << 1], to[n << 1], lc[m], rc[m], sum[m], mul[m];

inline int read()

inline int max(int x, int y)

inline int inc(int x, int y)

inline void add(int x, int y)

inline void dfs(int x)

}inline void push_down(int id)

inline int bui(int l, int r, int x)

inline int merge(int id1, int id2, int l, int r, int &x, int &y)

if (!id2)

if (l == r)

if (mul[id1] ^ 1) push_down(id1);

if (mul[id2] ^ 1) push_down(id2);

int mid = l + r >> 1;

lc[id1] = merge(lc[id1], lc[id2], l, mid, x, y), rc[id1] = merge(rc[id1], rc[id2], mid + 1, r, x, y);

sum[id1] = inc(sum[lc[id1]], sum[rc[id1]]);

return id1;

}inline int que(int id, int l, int r, int x)

inline void dfs1(int x)

}int main()

dfs(1);

m = read();

for (re i = 0; i < m; ++i)

dfs1(1);

printf("%d", que(rt[1], 0, ht, 0));

return 0;

}

NOI2020命運 線段樹合併優化樹形DP

考場時只想到暴力容斥,24分滾了 題目 dp方程怎麼來就不寫了,本文重點分析如何用線段樹合併及正確性 dp方程為 令dp u h 表示u的子樹中上端點已經處理好了,下段點在子樹中的,上段點最深為h的方案數,特別的,如果h 0,則代表已經處理好了 每當遇到乙個兒子v時都更新一遍dp陣列 dp u h ...

NOI2020 簡要題解

a 首先不難發現乙個暴力動態規劃的做法 記 f 表示第 i 天 當前在第 j 座城市所獲得的最大收益 有轉移方程 f max u cost i extra 發現 w 非常小 考慮拆點。將每個點拆成 w 個點 那麼一共會有至多 5n 個點。接著 考慮矩陣乘法 事實上 將求和運算改為 max 運算 並將...

NOI2020 去不了記

由於人菜,去不了。沒啥好說的。要是去了筆試都得掛分 kk 伺服器 之類的 zszz 了,不管了 開場先看 t1。怎麼又是 52501 一眼題?這個 52501 能不能敬業點啊,這個題比前兩年的不知道簽到多少倍了啊 再看 t2。似乎暴力分很多。再看 t3。欺負我 ynoi 刷的少 kk 然後去寫 t1...