[dp] [贪心] [ad-hoc] AT_agc069_d [AGC069D] Tree and Intervals

posted on 2025-04-23 13:24:24 | under | source

题意:给出 \(n\)。对于一棵树,定义 \(x_i=\sum\limits_{(u,v)\in E} [u\le i,v>i]\)。求 \(x_1\dots x_{n-1}\) 可能的取值方案。\(n\le 5\times 10^2\)

转化一下,先令所有点是白色,然后按序号从小到大染黑,则 \(x_i\) 即为异色边的数量,进一步可以视为同色连通块数量(减一)。

有了这个想法后,看看怎么判定 \(x\) 合法:记 \(s\) 为总连通块数量,\(c\) 为黑色连通块数量。

分讨 \(x_i\to x_{i+1}\) 时的变化:

  • \(x_i=x_{i+1}\)\(s\) 不变,只能是相邻两个异色连通块的边界的移动(在链上),故 \(c\) 不变。
  • \(x_i<x_{i+1}\)\(s\) 变大。若新增黑点与原有黑连通块相邻,则白色连通块增加、黑色连通块不增,贪心的最大化 \(c\)(原因见下文),所以黑块数 \(c\) 不变、白块被分裂而增加;若被白块包围,则新增单点黑块 \(c+1\)
  • \(x_i>x_{i+1}\)\(s\) 变小 \(k\)。若新增黑块刚好消灭一个单点白块,则 \(c-(k-1)\);若与原有黑块相邻,同上贪心地只让一个白块和它相邻,所以 \(c-k\)

合法当且仅当每个时刻 \(1\le c\le s\),且除了最后 \(c=s\) 外都应严格小于(这是因为必然存在白点)。不难感性理解。

因此贪心地最大化 \(c\) 以供后面消耗,在每种决策中贪心选取变化量最大的。注意“消灭单点白点”需满足是最后一步或操作前 \(s-c\ge 2\)。贪心是对的,因为最后一步之前再怎么搞 \(c\) 不可能 \(>s\),而最终又保证了 \(c=s=1\),所以 \(c\) 不会冗余。

设计 dp,记 \(f_{i,s,c}\) 为第 \(i\) 步时连通块状态为 \(c,s\)。直接根据上述讨论转移,注意 \(x_i>x_{i+1}\) 时可能被两种前继状态转移来。\(O(n^4)\)。前缀和优化为 \(O(n^3)\)

代码

#include<bits/stdc++.h>
using namespace std;

#define ADD(a, b) a = (a + b) % mod
const int N = 5e2 + 5;
int n, mod, f[N][N][N], s[N][N], s2[N][N];

signed main(){
    cin >> n >> mod;
    for(int i = 2; i <= n; ++i) f[1][i][1] = 1;
    for(int i = 1; i < n; ++i){
        for(int j = 1; j <= n; ++j)
            for(int k = 1; k <= n; ++k) s[j][k] = (f[i][j][k] + s[j - 1][k]) % mod;
        for(int j = n; j; --j)
            for(int k = n; k; --k) s2[j][k] = (f[i][j][k] + s2[j + 1][k + 1]) % mod;
        for(int j = 1; j <= n; ++j){
            for(int k = 1; k <= j - (i < (n - 1)); ++k){
                ADD(f[i + 1][j][k], f[i][j][k]);
                ADD(f[i + 1][j][k], s[j - 1][k - 1]);
                ADD(f[i + 1][j][k], s2[j + 1][k]);
                if(j - k == 1 && i < n - 1) ADD(f[i + 1][j][k], s2[j + 1][k + 1]);
            }
        }
    }
    cout << f[n][1][1];
    return 0;
}
posted @ 2026-01-12 19:59  Zwi  阅读(1)  评论(0)    收藏  举报