云斗学院 NOIP 考前练手公益赛 Round 1 题目分析
应该比CSP-S还简单。
T1
分讨+贪心即可。
T2
简单题,贪心
T3
原题,做过,USACO的题目,大概是只吃 \([l,r]\) 的,然后留一个 \(k\) 最后给一个牛吃。题单里面有。
T4
好玩。
我们假设我们并没有看到数据随机。
题目概述
给定一个有 \(n\) 个点的树,定义 \(F(\{S\})\) 表示连通 \(S\) 中所有点的最小边数。
求:
数据范围:\(1\leq n\leq 3\times 10^5\),保证数据随机。
分析
挺有意思的,千万不要往最小斯坦纳树那边想,想不出来的!
这种题目计算答案似乎很难去刻画,我一开始也只能想到 \(\mathcal{O}(n^2)\) 扫一遍 \(j\) 的做法。
但是很难去扩展。
考虑经典套路,如果某一个思考方向不行,我们考虑其他方向。
例如这道题目就是,这个直接计算 \(F\) 的方向不行,我们考虑其他跟 \(F\) 有关的方向。
那么我们考虑边。
一条边连接了两个连通块。
假设这条边是可以产生贡献的,那么当且仅当所需要查询的区间 \([l,r]\),不被包含在这两个连通块中的一个,考虑通过这个进行容斥。
那么我们假设每个 \(F\) 都需要 \(n-1\) 条边,那么答案就是 \(\frac{(n-1)n(n+1)}2\)。
考虑将一些边对一些区间的贡献删掉。
也就是说我们讨论那些被包含在两个连通块中的一个的区间有多少个即可。
那么枚举一个点,计算他和他的父亲那条边,那么就相当于求子树的贡献和不是子树的贡献,这显然是维护所有 \(0/1\) 连续段的贡献,所以直接用线段树合并即可。
由于数据随机,可以考虑 ODT(树上颜色段均摊),而且可以用 dsu on tree,但是时间复杂度没有这个优秀。
代码
时间复杂度 \(\mathcal{O}(n\log n)\),常数有点大,需要卡空间,注意垃圾回收的写法。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define N 300005
#define M 20
using namespace std;
struct node{
int lson,rson;
int lv,rv;
int ans0,ans1;
}tr[N * M];
#define ls(x) (tr[x].lson)
#define rs(x) (tr[x].rson)
int rub[N * M],rubsz;
int root[N],cnt,ans;
int newnode() {
if (!rubsz) return ++cnt;
else return rub[rubsz --];
}
void delnode(int x) {
if (!x) return;
rub[++rubsz] = x;
tr[x] = {0,0,0,0,0,0};
}
int calc(int len) {
return len * (len + 1) / 2;
}
int getlen(int val,int type) {
if (type == 1) return val > 0 ? val : 0;
else return val < 0 ? -val : 0;
}
void pushup(int x,int l,int r) {
int mid = l + r >> 1,lenL = (mid - l + 1),lenR = (r - mid);
int Ll1,Ll0,Lr1,Lr0,Lans0,Lans1;
if (tr[x].lson) {
Ll1 = getlen(tr[ls(x)].lv,1);
Lr1 = getlen(tr[ls(x)].rv,1);
Ll0 = getlen(tr[ls(x)].lv,0);
Lr0 = getlen(tr[ls(x)].rv,0);
Lans0 = tr[ls(x)].ans0,Lans1 = tr[ls(x)].ans1;
}
else {
Ll0 = Lr0 = lenL,Ll1 = Lr1 = 0;
Lans1 = 0,Lans0 = calc(lenL);
}
int Rl1,Rl0,Rr1,Rr0,Rans0,Rans1;
if (tr[x].rson) {
Rl1 = getlen(tr[rs(x)].lv,1);
Rr1 = getlen(tr[rs(x)].rv,1);
Rl0 = getlen(tr[rs(x)].lv,0);
Rr0 = getlen(tr[rs(x)].rv,0);
Rans0 = tr[rs(x)].ans0,Rans1 = tr[rs(x)].ans1;
}
else {
Rl0 = Rr0 = lenR,Rl1 = Rr1 = 0;
Rans1 = 0,Rans0 = calc(lenR);
}
tr[x].ans1 = Lans1 + Rans1;
if (Lr1 && Rl1) tr[x].ans1 = tr[x].ans1 - calc(Lr1) - calc(Rl1) + calc(Lr1 + Rl1);
int curl1 = Ll1,curr1 = Rr1;
if (Ll1 == lenL) curl1 += Rl1;
if (Rr1 == lenR) curr1 += Lr1;
tr[x].ans0 = Lans0 + Rans0;
if (Lr0 && Rl0) tr[x].ans0 = tr[x].ans0 - calc(Lr0) - calc(Rl0) + calc(Lr0 + Rl0);
int curl0 = Ll0,curr0 = Rr0;
if (Ll0 == lenL) curl0 += Rl0;
if (Rr0 == lenR) curr0 += Lr0;
if (curl1) tr[x].lv = curl1;
else tr[x].lv = -curl0;
if (curr1) tr[x].rv = curr1;
else tr[x].rv = -curr0;
}
void update(int &x,int l,int r,int pos) {
if (!x) x = newnode();
if (l == r) {
tr[x].lv = tr[x].rv = tr[x].ans1 = 1;
return;
}
int mid = l + r >> 1;
if (pos <= mid) update(ls(x),l,mid,pos);
else update(rs(x),mid + 1,r,pos);
pushup(x,l,r);
}
int merge(int a,int b,int l,int r) {
if (!a || !b) return a + b;
if (l == r) {
delnode(b);
return a;
}
int mid = l + r >> 1;
tr[a].lson = merge(ls(a),ls(b),l,mid);
tr[a].rson = merge(rs(a),rs(b),mid + 1,r);
pushup(a,l,r);
delnode(b);
return a;
}
int n;
vector<int> g[N];
void dfs(int cur,int fa) {
update(root[cur],1,n,cur);
for (auto i : g[cur])
if (i != fa) dfs(i,cur),root[cur] = merge(root[cur],root[i],1,n);
if (cur != 1) ans += tr[root[cur]].ans0 + tr[root[cur]].ans1;
}
signed main(){
cin >> n;
for (int i = 1;i < n;i ++) {
int u,v;
scanf("%lld%lld",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
int allans = n * (n + 1) / 2 * (n - 1);
dfs(1,0);
cout << allans - ans;
return 0;
}

浙公网安备 33010602011771号