LOJ #2462. 「2018 集训队互测 Day 1」完美的集合
题目链接
LOJ #2462. 「2018 集训队互测 Day 1」完美的集合
题目大意
有一棵 个点的带权树,树上每个节点有重量 和价值 ,在满足节点重量之和 的集合中,称那些价值之和最大的集合为完美集合。
现在要从所有完美集合中选出 个,要求这 个集合的并集中存在一个点 ,满足对于这些集合中任意一个点 ,都有 。求满足条件的方案数,答案对 取模。
,,,,树边权
Part 1
首先考虑求出完美集合的价值,直接做树上背包的话是 的,瓶颈在于需要合并两个区域的重量之和,要想得到较优的时间复杂度,可以设计一个状态,使得每次只需要决策单点的重量。于是我们先枚举一个点作为根,强制连通块包含该点,然后在树的 序上做 ,设 表示当前考虑到了 序上的第 个位置,已选取点重量总和为 时的最大价值和,记 为第 位对应的节点,则有:
若当前点不选,则子树可不能选,从而直接跳到 的位置继续 ,否则往下决策。
时间复杂度 。
Part 2
对于 个已经选好的完美集合,可以发现满足条件的点 组成了一个连通块(或空集),考虑容斥。对于每一个点,求出当前点可以作为 的选取 个集合方案数,对于每条边,求出两端点同时可以作为 的方案数,可以发现,每个选取方案都会被合法 的连通块中每个点和边都算一次,而在树上,点的数量 边的数量 ,从而我们拿点的方案数减去边的方案数,这样每个方案就恰好被计算一次了。
设 为当前限制下可以选取的完美集合数量,显然方案数即为 ,于是考虑计算 。这一点和之前的做法是极其相似的,以钦定的点为根(边就任取一端点), 改为存二元组 ,表示最大价值和为 ,达到 的集合数量为 ,然后转移和前面基本相同,不过若 当前点不满足 ,就强制不选,考虑边时另外一端点强制要选即可。
Part 3
现在得到了 ,我们要求出 对 取模的结果,可以发现这个质数 ,而 是 级别的, 是 级别的,显然无法通过一般的方式计算。
将 换成 ,注意到模数一个小质数的大幂次,于是分开计算 ,并将 拆解为 的指数和非 的倍数部分,非倍数部分存在逆元,为 ,指数部分直接相减(结果必然为正)然后乘到答案中即可。
可以递归求解,考虑求解 。
设多项式 ,我们要求的是 。注意到 ,如果这不明显,让 是 的倍数,就有 ,这是一个倍增的关系,容易联想到像快速幂那样,使用二进制拆解求出任意一个 的倍数 的 。
不仅如此,由于我们要求的是 ,当 时, 的非常数项全是 的倍数, 往后在模意义下便全部为 ,所以计算时只保留 的前 项便可。 而且 ,我们要求的 就是常数项, 时 的常数项变为 ,所以原来的函数 亦可以只保留前 位,这样我们在做多项式乘法时,暴力相乘就是很快的。
具体来说,我们先预处理出 这些多项式,计算 时若 不是 的倍数,就直接算 ,然后剩余部分直接乘到算出的 里,然后我们对 二进制分解,类似快速幂的步骤,先有一个初始多项式 ,遇到 中为 的位时将 更新为 ,最终 的常数项即为所求答案。
记 ,预处理是 的,计算一次 也是 的,求解 要递归 层,所以这一部分复杂度是 的。
综上,总时间复杂度为 , 左右,实际上要快一些。
总结
一道题用到了 个比较重要的技巧:
- 对于树上连通块的 可以使用对 序 的方式进行优化。
- 容斥问题中,若一个方案被一个树形连通块所有位置都计算到,可以使用点边容斥。
- 计算较大的二项式之类的系数时,可以考虑从多项式和倍增的角度去分析。
Code
LOJ 上的 Hack 数据是完美集合价值为 的情形,此时求完美集合数量时要去掉空集的情况。
#include<iostream>
#include<cstring>
#include<vector>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 62
#define V 10100
#define P 23
#define ll long long
#define lll __int128
#define mod 11920928955078125 // 5^23
#define phi 9536743164062500 // mod - mod/5
#define PLL pair<lll, lll>
#define fr first
#define sc second
using namespace std;
int n, M, K; ll Max;
int w[N], v[N];
int head[N], to[2*N], nxt[2*N];
int siz[N], p[N], dis[N][N];
int cnt, num;
ll Max_V;
PLL f[N][V];
lll C[P+2][P+2];
vector<lll> Pow[N+5];
void init(){ mem(head, -1), cnt = -1; }
void add_e(int a, int b, bool id){
nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b;
if(id) add_e(b, a, 0);
}
void dfs(int x, int fa){
siz[x] = 1, p[++num] = x;
for(int i = head[x]; ~i; i = nxt[i]){
if(to[i] == fa) continue;
dfs(to[i], x), siz[x] += siz[to[i]];
}
}
void Trans(PLL &a, PLL b){
if(a.sc < b.sc) a.fr = b.fr;
else if(a.sc == b.sc) a.fr += b.fr;
a.sc = max(a.sc, b.sc);
}
void dp(int root, int ex){
mem(f, 0);
f[1][0] = {1, 0};
rep(i,1,n) rep(j,0,M) if(f[i][j].fr){
int x = p[i];
if(x != ex) Trans(f[i+siz[x]][j], f[i][j]);
if(j+w[x] <= M && (ll)dis[root][x] * v[x] <= Max && (ll)dis[ex][x] * v[x] <= Max)
Trans(f[i+1][j+w[x]], {f[i][j].fr, f[i][j].sc + v[x]});
}
}
vector<lll> mul(vector<lll> &A, vector<lll> B){
if(A.empty() || B.empty()) return {};
int n = A.size(), m = B.size();
vector<lll> ret(min(n + m - 1, 23));
rep(i,0,n-1) rep(j,0,m-1) if(i+j < 23)
(ret[i+j] += A[i] * B[j]) %= mod;
return ret;
}
vector<lll> shift(vector<lll> &A, lll k){
int n = A.size();
vector<lll> ret(n);
rep(i,0,n-1){
lll pow = A[i];
rep(j,0,i) (ret[i-j] += pow * C[i][j]) %= mod, (pow *= k) %= mod;
}
return ret;
}
lll qpow(lll a, ll b){
lll ret = 1;
for(; b; b >>= 1){ if(b&1) (ret *= a) %= mod; (a *= a) %= mod; }
return ret;
}
void prework(){
C[0][0] = 1;
rep(i,1,P){
C[i][0] = 1;
rep(j,1,i) C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
}
Pow[0] = {1};
rep(i,1,4) Pow[0] = mul(Pow[0], {i, 1});
rep(i,1,N) Pow[i] = mul(Pow[i-1], shift(Pow[i-1], 5ll<<(i-1)));
}
PLL fact(lll n){
if(n == 0) return {1, 0};
if(n%5){
PLL p = fact(n-1);
return {p.fr * n % mod, p.sc};
}
lll exp = n/5, prod = 1;
PLL p = fact(n/5);
(prod *= p.fr) %= mod, (exp += p.sc) %= mod;
vector<lll> poly = {1};
per(i,N,0) if((n/5)>>i&1) poly = mul(Pow[i], shift(poly, 5ll<<i));
(prod *= poly[0]) %= mod;
return {prod, exp};
}
lll cal(lll n, lll m){
if(m < 0 || n < m) return 0;
PLL p = fact(n);
lll val = p.fr, exp = p.sc;
p = fact(n-m), (val *= qpow(p.fr, phi-1)) %= mod, exp -= p.sc;
p = fact(m), (val *= qpow(p.fr, phi-1)) %= mod, exp -= p.sc;
return (val * qpow(5, exp)) % mod;
}
int main(){
cin>>n>>M>>K>>Max;
rep(i,1,n) cin>>w[i];
rep(i,1,n) cin>>v[i];
init(), mem(dis, 0x3f);
int a, b, c;
rep(i,1,n-1){
cin>>a>>b>>c, add_e(a, b, 1);
dis[a][b] = c, dis[b][a] = c;
}
rep(i,1,n) dis[i][i] = 0;
rep(k,1,n) rep(i,1,n) rep(j,1,n)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
rep(i,1,n) dis[i][0] = dis[0][i] = 0;
rep(i,1,n){
num = 0, dfs(i, 0);
dp(0, 0);
rep(j,0,M) Max_V = max(Max_V, (ll)f[n+1][j].sc);
}
prework();
lll ans = 0;
rep(i,1,n+(cnt+1)/2){
num = 0;
if(i <= n) dfs(i, 0), dp(i, 0);
else dfs(to[(i-n-1)*2], 0), dp(to[(i-n-1)*2], to[(i-n-1)*2+1]);
lll num = 0;
rep(j,0,M) if(f[n+1][j].sc == Max_V) num += f[n+1][j].fr - (Max_V == 0 && j == 0);
(ans += i <= n ? cal(num, K) : mod - cal(num, K)) %= mod;
}
cout<< (ll)ans <<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?