結果
| 問題 |
No.196 典型DP (1)
|
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2024-01-21 20:58:12 |
| 言語 | Go (1.23.4) |
| 結果 |
AC
|
| 実行時間 | 41 ms / 2,000 ms |
| コード長 | 9,958 bytes |
| コンパイル時間 | 11,342 ms |
| コンパイル使用メモリ | 225,648 KB |
| 実行使用メモリ | 20,244 KB |
| 最終ジャッジ日時 | 2024-09-28 06:16:13 |
| 合計ジャッジ時間 | 12,610 ms |
|
ジャッジサーバーID (参考情報) |
judge4 / judge5 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 41 |
ソースコード
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// P2014选课()
// CF815C()
// abc287F()
// ABC207F()
yuki196()
}
// P2014 [CTSC1997] 选课, O(nk) dp
// https://www.luogu.com.cn/problem/U53204#submit
// https://www.luogu.com.cn/problem/P2014
// https://zhuanlan.zhihu.com/p/103813542
func P2014选课() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, k int
fmt.Fscan(in, &n, &k)
// 添加一个虚拟根节点,转换为选择k+1个节点的树形背包问题
DUMMY := n
graph := make([][]int, n+1)
scores := make([]int, n+1)
for i := 0; i < n; i++ {
var parent, score int
fmt.Fscan(in, &parent, &score)
parent--
if parent == -1 {
parent = DUMMY
}
graph[parent] = append(graph[parent], i)
scores[i] = score
}
dp := TreeKnapsackDpSquare1(graph, DUMMY, scores, k+1)
fmt.Fprintln(out, dp[k+1])
}
// https://www.luogu.com.cn/problem/CF815C
// CF815C Karen and Supermarket
// 有n件商品,每件有价格ci,优惠券di,
// 对于i>=2,使用di的条件为:xi的优惠券需要被使用
// 问初始金钱为money时 最多能买多少件商品? n<=5000,ci,di,money<=1e9
// !dp[u][j][0/1]是以u为根子树中,u用或者不用优惠券时,选j件物品需要的最小代价
//
// 因为使用优惠劵就必须买这件商品,我们可以将优惠劵的关系看成一棵树。
func CF815C() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
const INF int = 1e9 + 10
var n, money int
fmt.Fscan(in, &n, &money)
tree := make([][]int, n)
prices := make([]int, n)
coupons := make([]int, n)
for i := 0; i < n; i++ {
if i == 0 {
var price, coupon int
fmt.Fscan(in, &price, &coupon)
prices[i] = price
coupons[i] = coupon
} else {
var price, coupon, parent int
fmt.Fscan(in, &price, &coupon, &parent)
parent--
tree[parent] = append(tree[parent], i)
prices[i] = price
coupons[i] = coupon
}
}
subSize := make([]int, n)
var f func(int, int) [][2]int
f = func(cur, pre int) [][2]int {
subSize[cur] = 1
dp := make([][2]int, n+1)
for i := 0; i <= n; i++ {
dp[i][0] = INF
dp[i][1] = INF
}
dp[0][0] = 0
dp[1][0] = prices[cur]
dp[1][1] = prices[cur] - coupons[cur]
for _, next := range tree[cur] {
if next == pre {
continue
}
nextDp := f(next, cur)
// 当前节点可以不选, 所以是 j>=0
for j := subSize[cur]; j >= 0; j-- {
for w := 0; w <= subSize[next]; w++ {
dp[j+w][0] = min(dp[j+w][0], dp[j][0]+nextDp[w][0])
dp[j+w][1] = min(dp[j+w][1], dp[j][1]+nextDp[w][0])
dp[j+w][1] = min(dp[j+w][1], dp[j][1]+nextDp[w][1])
}
}
subSize[cur] += subSize[next]
}
return dp
}
rootDp := f(0, -1)
for i := n; i >= 0; i-- {
if rootDp[i][0] <= money || rootDp[i][1] <= money {
fmt.Fprintln(out, i)
return
}
}
}
// F - Components-连通块个数为i的导出子图数
// https://atcoder.jp/contests/abc287/tasks/abc287_f
// 给定一棵有n个点的树,在所有2^n−1的非空点集中,回答下列问题:
// !对于i∈[1,n],有多少个导出子图所形成的连通块个数恰好是i。
// 数量对 998244353取模。
// 1<=n<=5000
// 二乗の木DP(二乘木dp)
// https://snuke.hatenablog.com/entry/2019/01/15/211812
// 解:
// 合并子树的时候,如果点u和子树节点都选择的时候,连通块个数会减一,其余情况都不会
// 因此还要加上是否选择点 u的状态。
// !dp[i][k][0/1] 表示当前节点为i,当前连通块个数为k,当前节点是否被选中的状态下的方案数
func abc287F() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
const MOD int = 998244353
var n int
fmt.Fscan(in, &n)
edges := make([][2]int, n-1)
for i := 0; i < n-1; i++ {
fmt.Fscan(in, &edges[i][0], &edges[i][1])
edges[i][0]--
edges[i][1]--
}
components := func(n int, edges [][2]int) []int {
tree := make([][]int, n)
for i := 0; i < n-1; i++ {
u, v := edges[i][0], edges[i][1]
tree[u] = append(tree[u], v)
tree[v] = append(tree[v], u)
}
subSize := make([]int, n)
dp := make([][][2]int, n)
var dfs func(cur, pre int)
dfs = func(cur, pre int) {
subSize[cur] = 1
dp[cur] = make([][2]int, 2)
dp[cur][0][0] = 1
dp[cur][1][1] = 1
for _, next := range tree[cur] {
if next == pre {
continue
}
dfs(next, cur)
ndp := make([][2]int, subSize[cur]+subSize[next]+1) // 当前不选/当前选
dp1, dp2 := dp[cur], dp[next]
for i := 0; i <= subSize[cur]; i++ {
for j := 0; j <= subSize[next]; j++ {
ndp[i+j][0] += dp1[i][0] * (dp2[j][0] + dp2[j][1])
ndp[i+j][0] %= MOD
ndp[i+j][1] += dp1[i][1] * dp2[j][0]
ndp[i+j][1] %= MOD
if i+j-1 >= 0 {
ndp[i+j-1][1] += dp1[i][1] * dp2[j][1]
ndp[i+j-1][1] %= MOD
}
}
}
subSize[cur] += subSize[next]
dp[cur] = ndp
}
}
dfs(0, -1)
res := make([]int, n+1)
for i := 1; i <= n; i++ {
res[i] = (dp[0][i][0] + dp[0][i][1]) % MOD
}
return res
}
res := components(n, edges)
for i := 1; i <= n; i++ {
fmt.Fprintln(out, res[i])
}
}
// https://www.luogu.com.cn/remoteJudgeRedirect/atcoder/abc207_f
// [ABC207F] Tree Patrolling
// 类似二叉树监控
func ABC207F() {
}
// https://atcoder.jp/contests/arc130/tasks/arc130_d
// D - Zigzag Tree
func ARC130D() {}
// No.196 典型DP-涂黑k个顶点的方案数(树染色)
// https://yukicoder.me/problems/no/196
// 给定一棵n个点的树,每个顶点染黑或者白
// 求涂黑k个顶点的方案数模1e9+7,且满足黑色顶点的子树也是黑色
// n<=2000 0<=k<=n
// !dp[i][j] 表示以i为根的子树中,涂黑j个顶点的方案数
func yuki196() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
const MOD int = 1e9 + 7
var n, k int
fmt.Fscan(in, &n, &k)
tree := make([][]int, n)
for i := 0; i < n-1; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
tree[u] = append(tree[u], v)
tree[v] = append(tree[v], u)
}
dp := make([][]int, n)
subSize := make([]int, n)
var f func(int, int)
f = func(cur int, pre int) {
subSize[cur] = 1
dp[cur] = make([]int, 2)
dp[cur][0] = 1
dp[cur][1] = 0
for _, next := range tree[cur] {
if next == pre {
continue
}
f(next, cur)
ndp := make([]int, subSize[cur]+subSize[next]+1)
for i := 0; i <= subSize[cur]; i++ {
for j := 0; j <= subSize[next]; j++ {
ndp[i+j] += dp[cur][i] * dp[next][j]
ndp[i+j] %= MOD
}
}
subSize[cur] += subSize[next]
dp[cur] = ndp
}
dp[cur][len(dp[cur])-1] = 1 // !涂黑当前节点
}
f(0, -1)
fmt.Fprintln(out, dp[0][k])
}
// O(n*k) dp, 二乘木dp
// 树形背包/依赖背包,选取maxSelect个节点,使得选取的节点的权值和最大.
//
// tree: 树的邻接表表示
// root: 根节点
// scores: 节点的分数
// maxSelect: 选择的节点数,0<=maxSelect<=n
func TreeKnapsackDpSquare1(tree [][]int, root int, scores []int, maxSelect int) []int {
n := len(tree)
subSize := make([]int, n)
// dp[i][j] 表示以 i 为根的子树中选择 j 个节点的最大权值和
var f func(int, int) []int
f = func(cur int, pre int) []int {
subSize[cur] = 1
dp := make([]int, maxSelect+1)
dp[0] = 0
dp[1] = scores[cur]
for _, next := range tree[cur] {
if next == pre {
continue
}
nextDp := f(next, cur)
for j := subSize[cur]; j >= 1; j-- { // 当前节点必须选,所以是 j>=1;原地更新背包
for w := 0; w <= subSize[next]; w++ {
if j+w > maxSelect {
break
}
dp[j+w] = max(dp[j+w], dp[j]+nextDp[w])
}
}
subSize[cur] += subSize[next]
}
return dp
}
dp := f(root, -1)
return dp
}
// O(n*k) dp, 二乘木dp
// !滚动dp的写法
// 树形背包/依赖背包,选取maxSelect个节点,使得选取的节点的权值和最大.
//
// tree: 树的邻接表表示
// root: 根节点
// scores: 节点的分数
func TreeKnapsackDpSquare2(tree [][]int, root int, scores []int) []int {
n := len(tree)
dp := make([][]int, n) // dp[i][j] 表示以 i 为根的子树中选择 j 个节点的最大权值和
subSize := make([]int, n)
var f func(int, int)
f = func(cur int, pre int) {
subSize[cur] = 1
dp[cur] = make([]int, 2)
dp[cur][0] = 0
dp[cur][1] = scores[cur]
for _, next := range tree[cur] {
if next == pre {
continue
}
f(next, cur)
ndp := make([]int, subSize[cur]+subSize[next]+1)
// 当前节点必须选,所以是 i>=1
for i := 1; i <= subSize[cur]; i++ {
for j := 0; j <= subSize[next]; j++ {
ndp[i+j] = max(ndp[i+j], dp[cur][i]+dp[next][j])
}
}
subSize[cur] += subSize[next]
dp[cur] = ndp
}
}
f(root, -1)
return dp[root]
}
type Node = struct{ weight, value int }
// O(n*k^2) dp
// 树形背包/依赖背包,选取maxSelect个节点,使得选取的节点的权值和最大.
// nodes[i] 表示第 i 个节点的权值和分数.
func TreeKnapsackDp(tree [][]int, root int, nodes []Node, maxWeight int) []int {
var f func(int, int) []int
f = func(cur int, pre int) []int {
node := nodes[cur]
dp := make([]int, maxWeight+1) // dp[i] 表示选择容量为 i 时的最大价值
for i := node.weight; i <= maxWeight; i++ {
dp[i] = node.value // 根节点必须选
}
for _, next := range tree[cur] {
if next == pre {
continue
}
ndp := f(next, cur)
for j := maxWeight; j >= node.weight; j-- {
// 类似分组背包,枚举分给子树的容量 w,对应的子树的最大价值为 ndp[w]
// w 不可超过 j-node.weight,否则无法选择根节点
for w := 0; w <= j-node.weight; w++ {
dp[j] = max(dp[j], dp[j-w]+ndp[w])
}
}
}
return dp
}
return f(root, -1)
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min32(a, b int32) int32 {
if a < b {
return a
}
return b
}