[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;
}

浙公网安备 33010602011771号