首先本題貪心不是很好做,可以考慮 \(dp\)。
然後我們有了乙個很直接的想法,令 \(dp_\) 表示以 \(i\) 號點為根當前選擇的節點中權值最小的權值為 \(j\) 的最大成員數,可以發現這樣做是 \(o(n ^ 3)\) 的。可以發現這個 \(dp\) 有很多轉移是相同的,那麼我們這樣做是非常浪費的,為了能夠快速轉移,我們可以改變一下狀態,令 \(dp_\) 為以 \(i\) 為根的子樹內選擇的所有點權值都不小於 \(j\) 的最大成員數,可以發現這個 \(dp\) 的值從後往前是不斷遞增的,那麼對於每顆子樹的轉移我們就直接有 \(dp_ = \sum dp_\),如果在這個集合內選擇 \(u\) 則還有轉移 \(dp_ = \max\, dp_ + 1\}(i \le w_u)\),這樣就可以做到 \(o(n ^ 2)\) 了。
一些坑點:
#includeusing namespace std;
#define n 200000 + 5
#define m 4000000 + 5
#define ls t[p].l
#define rs t[p].r
#define mid (l + r >> 1)
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define next(i, u) for(int i = h[u]; i; i = e[i].next)
struct edgee[n << 1];
struct treet[m];
int n, u, ok, tot, cnt, num, d[n], w[n], h[n], rt[n];
int read()
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}void add(int u, int v)
void update(int &p, int l, int r, int x, int y, int k)
if(mid >= x) update(ls, l, mid, x, y, k);
if(mid < y) update(rs, mid + 1, r, x, y, k);
t[p].sum = t[ls].sum + t[rs].sum;
}int query(int p, int l, int r, int x, int y)
int find(int p, int l, int r, int k)
void merge(int &p, int k, int l, int r)
if(l == r)
merge(ls, t[k].l, l, mid), merge(rs, t[k].r, mid + 1, r);
t[p].sum = t[ls].sum + t[rs].sum;
}void dfs(int u, int fa)
int val = query(rt[u], 1, cnt, 1, w[u]);
int pos = find(rt[u], 1, cnt, val);
update(rt[u], 1, cnt, w[u], w[u], 1);
if(pos) update(rt[u], 1, cnt, pos, pos, -1);
}int main()
實際上還有可以使用另一種方式來維護這個差分陣列,那就是平衡樹啟發式合併,因為有 \(set\) 的存在這樣非常好寫。具體的我們合併兒子差分陣列直接啟發式合併暴力插入,由於那個需要差分單點修改的原因,為了方便我們在 \(set\) 中存入的每個元素 \(x\) 表示在 \(x\) 這個位置上的差分陣列 \(+1\),那麼我們的直接每次插入 \(w_i\) 這個元素,找到第乙個小於 \(w_i\) 的這個元素將這個元素刪除即可。最終的答案就是 \(1\) 號點 \(set\) 的大小。
#includeusing namespace std;
#define n 200000 + 5
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define next(i, u) for(int i = h[u]; i; i = e[i].next)
struct edgee[n << 1];
multiset s[n];
multiset :: iterator it;
int n, u, tot, cnt, h[n], d[n], w[n];
int read()
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}void add(int u, int v)
void merge(int x, int y)
void dfs(int u, int fa)
s[u].insert(w[u]);
it = s[u].lower_bound(w[u]);
if(it != s[u].end() && it != s[u].begin()) s[u].erase(--it);
}int main()
實際上有這樣乙個套路:
對於這種 \(dp\) 值實際上是乙個一次函式或常數函式的 \(dp\) 可以先差分然後再使用 \(set\) 或線段樹合併來維護,因為線段樹可以支援區間加如果要維護的東西涉及區間修改那麼可以使用線段樹合併,而類似at2347 [arc070c] narrowrectangles可以用 \(set\) 來維護拐點支援函式平移。
FJOI2018 領導集團問題
fjoi2018 領導集團問題 dp i j i為根子樹,最上面的值是j,選擇的最大值 觀察dp方程 1.整體dp已經可以做了。2.考慮優美一些的做法 dp i 如果對j取字尾最大值,顯然是不上公升的分段函式 而段數就是子樹sz 樹形dp的時候,子樹之間可以直接把分段函式按位相加。對於 w x 的,...
FJOI2018 領導集團問題
給定帶點權樹,求最大的集合使得,集合內若兩點為祖孫關係,孫子權值 le 祖先權值 令 f 為 u 子樹內選擇 i 個點,最小值最大是多少,轉移顯然 考慮對每個點維護乙個可重集 s u 降序,第 i 個點為子樹內選擇 i 個點,最小值的最大可能值 合併兩個子樹 s s 直接合併集合即可 考慮將 u 加...
FJOI2018 領導集團問題
題解我們可以先搞乙個 dp 出來,dp u i 表示以 u 號節點為根的子樹,選擇集合元素中最小的不小於 i 的最優方案。然後我們可以發現這個 dp 是自帶乙個字尾 max 的,然後我們把它向後差分一下去維護。那麼觀察到對於乙個節點,它的所有子樹之間是互不影響的,所以我們直接把他們對應位置加起來就好...