P12480 [集训队互测 2024] Classical Counting Problem 题解
P12480 [集训队互测 2024] Classical Counting Problem 题解
Update on 2026.1.9:发现自己之前写了很久的点分治复杂度都不太对,写假了,于是修改了代码中的 divide 部分,具体就是在每次找到中心之后重新 getroot 一遍求出正确的
P12480 [集训队互测 2024] Classical Counting Problem - 洛谷
题意
给定一棵
- 选择当前树上编号最大或最小的点,删除它以及以它为端点的所有边,并任选一个连通块作为新的树。
- 记
min 为当前树上编号最小值,max 为当前树上编号最大值,size 为树上节点数量。定义一棵树的权值为max \times min \times size ,求能通过上述操作得到的非空树的权值和,答案对2^{32} 取模。
Solution
拿到题目,很难找到入手点,因此我们先来考虑一个性质:
- 一个
min 和一个max 能够确定唯一的树。
我们考虑它的条件,
此时我们容易得到一个
发现还需要继续优化,考虑拆贡献,可以将权值中的
考虑
考虑点分治,对于每个风车,在风车上所有点中,点分树上深度最小的点上统计答案。假设红色点
其中
接下来问题变成,我们如何统计子树内所有
考虑扫描线,对于每个
考虑该怎么处理得到的限制关系。把子树内每个点
- 对于每个位置
i ,l = i 的l 的编号和sum 。 - 对于每个位置
i ,mi_x = i 的x 的数量cnt 。 - 对于每个位置
i ,l = i 的合法(l,x) 对的贡献和val 。
那么对于每个限制
接下来考虑如何统计
-
这个点作为
x ,此时对于l \le mi_x 的所有l ,x 都能与之配对产生贡献。对于所有合法的l ,它能增加的贡献值也为l ,要对这些l 的val 加上他们对应的sum 。同时插入x 后,mi_x 处的cnt 也要+1 。 -
这个点作为
l ,此时我们需要计算这个l 能和多少x 产生贡献,发现就是询问mi_x \ge l 的x 的数量,由于前面我们对每个位置i 维护了mi_x = i 的x 的数量,这里可以直接区间查询,同时l 位置的编号和也需要增加。
对于第一类,我们需要对贡献值区间加对应的
对于第二类,需要询问区间
想通如何维护之后这道题目就很简单了,实现不难,维护出需要的信息即可。每层维护线段树,复杂度为
实现细节
- 注意到模数是
2^{32} ,可以直接使用 unsigned int 存储,这样就不需要取模了。 - 对于每层值域需要离散化,否则每层都是
O(n\log n) 的,总时间复杂度会变成O(n^2 \log^2 n) 。 - 要先加入
l,x ,再询问r 。 - 由于需要去重,如果对每棵子树减去贡献,注意路径
\min 和\max 的初始值。 - 该清空的要清空,该赋值的要赋值。
代码(常数巨大)
#include<bits/stdc++.h>
using namespace std;
#define int unsigned int
int n,ans,rt,tot;
const int maxn = 1e5 + 10;
bool vis[maxn];
vector<int> G[maxn];
inline int read()
{
int x = 0,f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') f = -1;
ch = getchar();
}
while(ch <= '9' && ch >= '0')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
inline void print(int x)
{
if(x > 9) print(x / 10);
putchar(x % 10 + '0');
}
struct note{
int s[3],tag; // s[0]:最小值之和, s[1]:计数, s[2]:贡献和
note() {s[0] = s[1] = s[2] = 0;}
}tr[maxn * 13];
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define mid ((l + r) >> 1)
void pushup(int rt)
{
for(int i = 0;i < 3;i++)
tr[rt].s[i] = tr[lson].s[i] + tr[rson].s[i];
}
void build(int rt,int l,int r)
{
if(l == r)
{
for(int i = 0;i < 3;i++) tr[rt].s[i] = 0;
tr[rt].tag = 0;
return;
}
build(lson,l,mid);
build(rson,mid + 1,r);
pushup(rt);
}
void pushdown(int rt)
{
if(tr[rt].tag)
{
// s[2] += tag * s[0] (最小值之和)
tr[lson].s[2] = tr[lson].s[2] + tr[rt].tag * tr[lson].s[0];
tr[rson].s[2] = tr[rson].s[2] + tr[rt].tag * tr[rson].s[0];
tr[lson].tag += tr[rt].tag;
tr[rson].tag += tr[rt].tag;
tr[rt].tag = 0;
}
}
void update(int rt,int l,int r,int x,int k,int id)
{
if(l == r)
{
tr[rt].s[id] = tr[rt].s[id] + k;
return;
}
pushdown(rt);
if(x <= mid) update(lson,l,mid,x,k,id);
else update(rson,mid + 1,r,x,k,id);
pushup(rt);
}
void upd(int rt,int l,int r,int x,int y,int k)
{
if(x <= l && r <= y)
{
tr[rt].s[2] = tr[rt].s[2] + k * tr[rt].s[0];
tr[rt].tag += k;
return;
}
pushdown(rt);
if(x <= mid) upd(lson,l,mid,x,y,k);
if(y > mid) upd(rson,mid + 1,r,x,y,k);
pushup(rt);
}
note query(int rt,int l,int r,int x,int y)
{
if(x <= l && r <= y) return tr[rt];
pushdown(rt);
note res, lss, rss;
if(x <= mid)
{
lss = query(lson,l,mid,x,y);
for(int i = 0;i < 3;i++) res.s[i] += lss.s[i];
}
if(y > mid)
{
rss = query(rson,mid + 1,r,x,y);
for(int i = 0;i < 3;i++) res.s[i] += rss.s[i];
}
return res;
}
int siz[maxn],f[maxn];
void getroot(int u,int fat)
{
siz[u] = 1;f[u] = 0;
for(int v : G[u])
{
if(v == fat || vis[v]) continue;
getroot(v,u);
siz[u] += siz[v];
f[u] = max(f[u],siz[v]);
}
f[u] = max(f[u],tot - siz[u]);
if(f[u] < f[rt]) rt = u;
}
vector<int> vec;
int m,a[maxn * 3];
vector<int> q[maxn * 3];
int mi[maxn],mx[maxn];
void dfs(int u,int fa)
{
mi[u] = min(mi[fa],u);
mx[u] = max(mx[fa],u);
a[++m] = mx[u];
a[++m] = mi[u];
a[++m] = u;
vec.push_back(u);
for(int v : G[u]) if(!vis[v] && v != fa) dfs(v,u);
}
int getans(int root,int fa)
{
int res = 0;m = 0;
vec.clear();dfs(root,fa); // 求出子树信息
sort(a + 1,a + m + 1);
m = unique(a + 1,a + m + 1) - a - 1;
build(1,1,m);// 线段树建树是 O(size)
for(int i = 1;i <= m;i++) q[i].clear();
for(auto u : vec)
{
int wz = lower_bound(a + 1,a + m + 1,mx[u]) - a;
q[wz].push_back(u);// 离线扫描线
}
for(int i = 1;i <= m;i++)
{
for(int u : q[i]) // add
{
// u 作为 x
int mip = lower_bound(a + 1,a + m + 1,mi[u]) - a;
update(1,1,m,mip,1,1);
upd(1,1,m,1,mip,1);
// u 作为 l
if(mi[u] == u)
{
int p = lower_bound(a + 1,a + m + 1,u) - a;
update(1,1,m,p,u,0);
int ss = query(1,1,m,p,m).s[1];
update(1,1,m,p,ss * u,2);
}
}
for(int u : q[i]) // query
{
if(mx[u] == u)
{
int mip = lower_bound(a + 1,a + m + 1,mi[u]) - a;
res += query(1,1,m,1,mip).s[2] * u;
}
}
}
return res;
}
void divide(int u)
{
mi[u] = mx[u] = u;
ans += getans(u,u);
vis[u] = 1;
for(int v : G[u])
{
if(vis[v]) continue;
ans -= getans(v,u);
}
for(int v : G[u])
{
if(vis[v]) continue;
tot = siz[v];
f[rt = 0] = 1e9;
getroot(v,0);
getroot(rt,0);
divide(rt);
}
}
int solve()
{
n = read();
for(int i = 1;i < n;i++)
{
int u = read(),v = read();
G[u].push_back(v);
G[v].push_back(u);
}
tot = n;
f[rt = 0] = 1e9;
getroot(1,0);
getroot(rt,0); // 重新计算size
divide(rt);
print(ans);puts("");
return 0;
}
signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);cout.tie(0);
int t = read();
while(t--)
{
solve();
// 清空
for(int i = 1;i <= n;i++) G[i].clear(),vis[i] = 0;
ans = 0;
}
return 0;
}
希望对你有所帮助。
本人
祝各位 CSP - S rp++。
NOIP 已经打完了,祝各位省选 2026 rp++ 吧!