这场考试感觉很奇怪。
T
1
、
T
2
T1、T2
T1、T2 都缺一个小特判。
T
3
T3
T3 打了个比暴力优的暴力 还是暴力,但是不知道为什么
W
A
WA
WA 穿了。
考试的时候还玩扫雷…
其实,菜是原罪啊…
T1 小奇取石子
题目
考场思路
刚开始差点被自己坑了,开考
5
m
i
n
5min
5min 就码出了一个可以惨痛爆零的
01
01
01 背包。
结果还好的是交卷前几分钟自己出了个小数据卡掉自己,然后就码出一个
80
p
t
s
80pts
80pts 的代码。
首先根据数据将方法分开。
对于
A
、
B
A、B
A、B 两组数据,很明显暴力都可以过,这个没有问题。
对于
C
C
C 组数据,定义
d
p
[
i
]
dp[i]
dp[i]:得到石子数为
i
i
i 时的最小选择石子堆。
状转见代码。
#include<cstdio>
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define cg (c=getchar())
inline int qread(){
int x=0,f=1;char c;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return x*f;
}
template<class T>inline void qread(T& x){
x=0;char c;bool f=0;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=200;
const int MAXK=2500;
const int INF=0x3f3f3f3f;
int n,m,k,a[MAXN+5],maxx;
int dp[MAXK+5];
inline void init(){
qread(n,m,k);
for(int i=1;i<=n;++i)qread(a[i]);
}
void dfs(const int i,const int x,const int w){
if(w>k||x>m)return;
maxx=Max(maxx,w);
if(i>n)return;
dfs(i+1,x+1,w+a[i]);
dfs(i+1,x,w);
}
inline void getDp(){
rep(i,1,k)dp[i]=INF;
rep(i,1,n)rep(j,a[i],k)dp[j]=Min(dp[j],dp[j-a[i]]+1);
dep(i,k,1)if(dp[i]<=m){maxx=i;break;}
}
signed main(){
// freopen("stone.in","r",stdin);
// freopen("stone.out","w",stdout);
init();
if(n<=20)dfs(1,0,0);
else getDp();
printf("%d\n",maxx);
return 0;
}
正解
其实就是我的考场代码改个细节,为了保存上一个
i
i
i 的状态,
j
j
j 应该从大到小枚举。
不知道我考场的时候脑子 what
了,这个细节都打错了…
#include<cstdio>
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define cg (c=getchar())
inline int qread(){
int x=0,f=1;char c;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return x*f;
}
template<class T>inline void qread(T& x){
x=0;char c;bool f=0;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=200;
const int MAXK=2500;
const int INF=0x3f3f3f3f;
int n,m,k,a[MAXN+5],maxx;
int dp[MAXK+5];
inline void init(){
qread(n,m,k);
for(int i=1;i<=n;++i)qread(a[i]);
}
void dfs(const int i,const int x,const int w){
if(w>k||x>m)return;
maxx=Max(maxx,w);
if(i>n)return;
dfs(i+1,x+1,w+a[i]);
dfs(i+1,x,w);
}
inline void getDp(){
rep(i,1,k)dp[i]=INF;
rep(i,1,n)dep(j,k,a[i])dp[j]=Min(dp[j],dp[j-a[i]]+1);
dep(i,k,1)if(dp[i]<=m){maxx=i;break;}
}
signed main(){
// freopen("stone.in","r",stdin);
// freopen("stone.out","w",stdout);
init();
if(n<=20)dfs(1,0,0);
else getDp();
printf("%d\n",maxx);
return 0;
}
T2 「CCO 2017」专业网络
题目
考场思路
所谓信息竞赛,其实就是面向数据编程
看看数据范围,发现前两组很好骗分,然后就可以对于这两组数据进行骗分了…
#include<cstdio>
#include<algorithm>
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
using namespace std;
#define cg (c=getchar())
inline int qread(){
int x=0,f=1;char c;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return x*f;
}
template<class T>inline void qread(T& x){
x=0;char c;bool f=0;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=2e5;
const int INF=0x3f3f3f3f;
int N,maxb,ans=INF;
struct node{
int a,b;
inline void input(){qread(a,b);if(a==0)b=0;}
node(){}
node(const int A,const int B):a(A),b(B){}
bool operator<(const node t){return b==t.b?a<t.a:b<t.b;}
}p[MAXN+5];
inline void init(){
qread(N);
rep(i,1,N)p[i].input(),maxb=Max(maxb,p[i].b);
}
inline void dfs30(const int,const int,const int);
inline bool cmp30(const node _x,const node _y){
return _x.a==_y.a?_x.b<_y.b:_x.a<_y.a;
}
inline void pts30(){
sort(p+1,p+N+1,cmp30);
dfs30(0,0,0);
}
inline void dfs30(int tot,int state,const int cost){
if(tot==N)return (void)(ans=Min(ans,cost));
if(cost>ans)return;
rep(i,1,N)if(!(state&(1<<(i-1)))&&tot>=p[i].a)++tot,state|=(1<<(i-1));
if(tot==N)return (void)(ans=Min(ans,cost));
rep(i,1,N)if(!(state&(1<<(i-1))))
dfs30(tot+1,state|(1<<(i-1)),cost+p[i].b);
}
inline bool cmp15(const node _x,const node _y){
return _x.a>_y.a;
}
inline void pts15(){
sort(p+1,p+N+1,cmp15);
int l=1,r=N,tot=0;ans=0;
while(l<=r){
while(p[r].a<=tot)--r,++tot;
if(r<l)break;
if(p[l].a>tot)++ans;
++l,++tot;
}
}
signed main(){
// freopen("network.in","r",stdin);
// freopen("network.out","w",stdout);
init();
if(maxb==1)pts15();
else pts30();
printf("%d\n",ans);
return 0;
}
题解
其实,这道题跟 《信息奥赛一本通》中的不守交规 有异曲同工之妙。—— JZM \text{JZM} JZM 大佬
首先,我们跟每一个人建立友谊关系的状态肯定唯一。
假如我们跟
i
i
i 交朋友之前,我们已经交了
j
j
j 个朋友。
那么,肯定不存在
k
(
k
≠
i
)
k(k\neq i)
k(k=i) 使得我们在与
k
k
k 交朋友之前已经交了
j
j
j 个朋友。
所以,我们的
j
j
j 可以取
[
0
,
N
−
1
]
[0,N-1]
[0,N−1] 之中的数。
那么,我们只需要在交这
N
N
N 个朋友的时候,将他们对应到这
[
0
,
N
−
1
]
[0,N-1]
[0,N−1] 的
N
N
N 个数中去即可。
而如果
i
i
i 是不用花费的,那么它的对应值一定在
[
A
i
,
N
−
1
]
[A_i,N-1]
[Ai,N−1] 中的一个数。
贪心地,我们首先要满足那些
B
i
B_i
Bi 较大的数,这样我们的花费就会尽可能的少。
而为了让区间能够对应的数尽可能多,我们的搜索需要从
A
i
A_i
Ai 开始枚举。
而对于一个
i
i
i,如果在
[
A
i
,
N
−
1
]
[A_i,N-1]
[Ai,N−1] 中已经没有数没有被对应到,那么它就一定需要支付费用。
那么这一段的代码也就很好写了
for(int i=1,loc;i<=N;++i){
bool f=true;
for(int j=p[i].a;j<N;++j)if(!vis[j]){
vis[j]=true,f=false;
break;
}
if(!f)ans+=p[i].b;
}
然而这是
O
(
N
2
)
O(N^2)
O(N2) 的算法,它最多只能得到
60
p
t
s
60pts
60pts,因此我们需要用到并查集优化。
时间复杂度接近于
O
(
N
)
O(N)
O(N),我省掉了反阿克曼函数,它增长地太慢了。
#include<cstdio>
#include<algorithm>
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
using namespace std;
#define cg (c=getchar())
inline int qread(){
int x=0,f=1;char c;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return x*f;
}
template<class T>inline void qread(T& x){
x=0;char c;bool f=0;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=2e5;
int N,ans,pre[MAXN+5];bool vis[MAXN+5];
struct node{
int a,b;
inline void input(){qread(a,b);}
node(){}
node(const int A,const int B):a(A),b(B){}
bool operator<(const node t){return b==t.b?a<t.a:b>t.b;}
}p[MAXN+5];
inline void init(){
qread(N);
for(int i=1;i<=N;++i)p[i].input();
sort(p+1,p+N+1);
}
inline int findSet(const int u){
return u==pre[u]?u:pre[u]=findSet(pre[u]);
}
inline void calc(){
for(int i=1;i<=N;++i)pre[i]=i;
for(int i=1,loc;i<=N;++i){
loc=findSet(p[i].a);
if(loc>=N)ans+=p[i].b;
else pre[loc]=loc+1;
}
}
signed main(){
init();
calc();
printf("%d\n",ans);
return 0;
}
T3 「ZJOI2017」线段树
题目
考场思路
考试的时候,我想到一种时间复杂度只有
O
(
n
m
)
O(nm)
O(nm) 的暴力算法 虽然还是暴力,但是时间少了点
但是我没有仔细思考其正确性。
大致思路是这样的:
在询问区间
[
l
,
r
]
[l,r]
[l,r] 的时候,假如有某一段区间
[
l
′
,
r
′
]
[l',r']
[l′,r′] 与
u
u
u 不在大树的同一棵子树上,那么就计算区间
[
l
′
,
r
′
]
[l',r']
[l′,r′] 的贡献。
但是这样做有问题,假若区间
[
l
′
,
r
′
′
]
[l',r'']
[l′,r′′] 被包含于
[
l
,
r
]
[l,r]
[l,r] ,并且
r
′
r'
r′ 与
r
′
′
r''
r′′ 不在同一棵子树内,那么我的算法就会使得答案
a
n
s
ans
ans 变大。
因为这样做会使我寻找的可以被拼成区间
[
l
,
r
]
[l,r]
[l,r] 的点变多,自然而然,
a
n
s
ans
ans 也就变大了。
附个代码
#include<cstdio>
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define cg (c=getchar())
inline int qread(){
int x=0,f=1;char c;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return x*f;
}
template<class T>inline void qread(T& x){
x=0;char c;bool f=0;
while(cg<'0'||'9'<c)if(c=='-')f=-1;
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=2e5;
struct node{
int l,r,mid,d,lc,rc;
node(){lc=rc=0;}
node(const int L,const int R,const int M,const int D):l(L),r(R),mid(M),d(D){lc=rc=0;}
}tre[(MAXN<<1)+5];
int N,q,ncnt,ans;
int s[(MAXN<<1)+5],tail;
inline int buildtre(const int pre,const int l,const int r,const int d){
int now=++ncnt;
if(l==r)return tre[now]=node(l,r,0,d),now;
tre[now]=node(l,r,qread(),d);
tre[now].lc=buildtre(now,l,tre[now].mid,d+1);
tre[now].rc=buildtre(now,tre[now].mid+1,r,d+1);
return now;
}
inline void init(){
qread(N);
buildtre(0,1,N,1);
}
void getNode(const int i,const int l,const int r){
if(l<=tre[i].l&&tre[i].r<=r)return (void)(s[++tail]=i);
if(l<=tre[i].mid)getNode(tre[i].lc,l,r);
if(r>tre[i].mid)getNode(tre[i].rc,l,r);
}
inline void calc(const int lca,const int u){
while(tail>0)ans+=tre[s[tail--]].d+tre[u].d-2*tre[lca].d;
}
void algor(const int i,const int u,const int l,const int r){
// printf("algor : %d %d %d %d\n",i,u,l,r);
if(u==i){
getNode(i,l,r);
calc(i,i);
return;
}
if(tre[i].lc<=u&&u<tre[i].rc&&r<=tre[i].mid)algor(tre[i].lc,u,l,r);
else if(u>=tre[i].rc&&l>tre[i].mid)algor(tre[i].rc,u,l,r);
//以上皆为同方向的子树时
else{
//在不同向
//此时一定可以保证, i 即为他们的 lca
if(u>=tre[i].rc&&l<=tre[i].mid){//当询问区间的 部分/全部 在左边, 点在右边时
getNode(tre[i].lc,l,Min(tre[i].mid,r));
calc(i,u);
if(r>tre[i].mid)algor(tre[i].rc,u,tre[i].mid+1,r);
}
else if(tre[i].lc<=u&&u<tre[i].rc&&r>tre[i].mid){//当询问区间的 部分/全部 在右边, 点在左边时
getNode(tre[i].rc,Max(tre[i].mid+1,l),r);
calc(i,u);
if(l<=tre[i].mid)algor(tre[i].lc,u,l,tre[i].mid);
}
/*
else if(u==i){//当点就是当前的点的时候, 直接在其子树中寻找区间的点进行计算
getNode(i,l,r);
calc(i,i);
}
*/
}
}
inline void getQuery(){
int q=qread(),u,l,r;
while(q--){ans=0;
qread(u,l,r);
algor(1,u,l,r);
printf("%d\n",ans);
}
}
signed main(){
// freopen("0.in","r",stdin);
// freopen("tree.out","w",stdout);
init();
// puts("finished input!");
getQuery();
return 0;
}
正解
一道编码较为困难的数据结构题。
首先,我们解决询问的区间的问题。
看下面这张图
其实这个就是题目描述里面的那张图。
假设我们需要访问区间
[
2
,
4
]
[2,4]
[2,4],应该怎么做呢?
方法一
你可以肉眼看…虽然这样好像不能交到
O
J
OJ
OJ 上去…
但是我们可以看出我们要找的节点是
(
2
−
3
)
(2-3)
(2−3) 与
(
4
−
4
)
(4-4)
(4−4)。
方法二
使用类似于普通线段树的方法进行区间查找,这样的复杂度对于这样的广义线段树来说大概是
O
(
n
)
O(n)
O(n) 的。
方法三
可以用类似于
zkw
\text{zkw}
zkw 线段树 的方式。
我们要访问区间
[
2
,
4
]
[2,4]
[2,4] ,那么左边从
(
1
−
1
)
(1-1)
(1−1) 开始,右边从
(
5
−
5
)
(5-5)
(5−5) 开始,一起往上爬。
如果左边点往上爬,发现它是父亲节点的左儿子,那么其父节点的右儿子是一定是我们要找的点。
如果右边点往上爬,发现他是父节点的右儿子,那么其父节点的左儿子是一个是我们要找的点。
大概搜索的结果就是
其中,被红色笔圈起来的点是我们要特殊注意的,而蓝色下划线是我们要取到的点。
那么这样做的时间复杂度?不用说,
O
(
n
)
O(n)
O(n)。
先把这些方法放在一边,看一看我们需要求什么。
题目似乎给出
a
n
s
=
∑
v
∈
S
[
l
,
r
]
d
i
s
(
u
,
v
)
ans=\sum_{v\in S[l,r]}dis(u,v)
ans=v∈S[l,r]∑dis(u,v)令
d
[
u
]
d[u]
d[u]:点
u
u
u 的深度。
我们可以将
d
i
s
(
u
,
v
)
dis(u,v)
dis(u,v) 换成用
l
c
a
lca
lca (时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn) 左右,不要忽略了)来表示,那么就有
a
n
s
=
∑
v
∈
S
[
l
,
r
]
d
[
u
]
+
d
[
v
]
−
2
⋅
d
[
l
c
a
(
u
,
v
)
]
ans=\sum_{v\in S[l,r]}d[u]+d[v]-2\cdot d[lca(u,v)]
ans=v∈S[l,r]∑d[u]+d[v]−2⋅d[lca(u,v)]假设我们最后可以处理出,我们找到的满足
v
∈
S
[
l
,
r
]
v\in S[l,r]
v∈S[l,r] 的点共有
t
t
t 个,那么这个公式可以再展开:
a
n
s
=
t
⋅
d
[
u
]
+
∑
v
∈
S
[
l
,
r
]
d
[
v
]
−
∑
v
∈
S
[
l
,
r
]
2
⋅
d
[
l
c
a
(
u
,
v
)
]
ans=t\cdot d[u]+\sum_{v\in S[l,r]}d[v]-\sum_{v\in S[l,r]}2\cdot d[lca(u,v)]
ans=t⋅d[u]+v∈S[l,r]∑d[v]−v∈S[l,r]∑2⋅d[lca(u,v)]然而这个式子似乎再也不能往下化简了。
我们再往回看,我们已分析出的两种方法 人为忽略第一种
假若我们用 方法二
,那么我们似乎并不能做什么优化,只有用标准的线段树做法,时间复杂度还是
O
(
n
)
O(n)
O(n)。
假若我们用 方法三
,那么我们似乎可以用树上差分。
怎么个差分法呢?
记录一下每个点的信息
- t l s [ u ] tls[u] tls[u]:从根到 u u u 一共有多少左儿子是没有经过的
- t r s [ u ] trs[u] trs[u]:从根到 u u u 一共有多少右儿子是没有经过的
- t d l s [ u ] tdls[u] tdls[u]:从根到 u u u 一共没见过的左儿子的深度之和
- t d r s [ u ] tdrs[u] tdrs[u]:从根到 u u u 一共没见过的右儿子的深度之和
那么我们怎么求以上内容呢?
可以在建树的时候顺便处理出来。
假设我们有一个点
f
a
fa
fa,其深度为
d
d
d,它的左儿子是
l
c
lc
lc,右儿子是
r
c
rc
rc。
那么,若
r
c
rc
rc 存在,且
l
c
lc
lc 存在,则满足
t
r
s
[
l
c
]
=
t
r
s
[
f
a
]
+
1
,
t
d
r
s
[
l
c
]
=
t
d
r
s
[
f
a
]
+
(
d
+
1
)
,
t
l
s
[
l
c
]
=
t
l
s
[
f
a
]
,
t
d
l
s
[
l
c
]
=
t
d
l
s
[
f
a
]
trs[lc]=trs[fa]+1,tdrs[lc]=tdrs[fa]+(d+1),tls[lc]=tls[fa],tdls[lc]=tdls[fa]
trs[lc]=trs[fa]+1,tdrs[lc]=tdrs[fa]+(d+1),tls[lc]=tls[fa],tdls[lc]=tdls[fa]而若
r
c
rc
rc 不存在,则有
t
r
s
[
l
c
]
=
t
r
s
[
f
a
]
,
t
d
r
s
[
l
c
]
=
t
d
r
s
[
f
a
]
,
t
l
s
[
l
c
]
=
t
l
s
[
f
a
]
,
t
d
l
s
[
l
c
]
=
t
d
l
s
[
f
a
]
trs[lc]=trs[fa],tdrs[lc]=tdrs[fa],tls[lc]=tls[fa],tdls[lc]=tdls[fa]
trs[lc]=trs[fa],tdrs[lc]=tdrs[fa],tls[lc]=tls[fa],tdls[lc]=tdls[fa]如果
l
c
lc
lc 存在,且
r
c
rc
rc 存在,则满足
t
r
s
[
r
c
]
=
t
r
s
[
f
a
]
,
t
d
r
s
[
r
c
]
=
t
d
r
s
[
f
a
]
,
t
l
s
[
r
c
]
=
t
l
s
[
f
a
]
+
1
,
t
d
l
s
[
r
c
]
=
t
d
l
s
[
f
a
]
+
(
d
+
1
)
trs[rc]=trs[fa],tdrs[rc]=tdrs[fa],tls[rc]=tls[fa]+1,tdls[rc]=tdls[fa]+(d+1)
trs[rc]=trs[fa],tdrs[rc]=tdrs[fa],tls[rc]=tls[fa]+1,tdls[rc]=tdls[fa]+(d+1)然后,我们就可以在建树,或者是输入建树时预处理出以上内容即可。
接着之前说的树上差分。
假若我们要求区间
[
a
,
b
]
[a,b]
[a,b] ,那么我们就从
(
a
−
1
,
a
−
1
)
(a-1,a-1)
(a−1,a−1) 与
(
b
+
1
,
b
+
1
)
(b+1,b+1)
(b+1,b+1) 开始往上爬。
找到它们的最近公共祖先
l
c
a
lca
lca,那么我们要求的公式中的
t
、
∑
v
∈
S
[
l
,
r
]
d
[
v
]
t、\sum_{v\in S[l,r]}d[v]
t、∑v∈S[l,r]d[v] 都可以用树上差分解决。
即
t
t
t 满足
t
=
t
r
s
[
l
e
a
f
u
[
a
−
1
]
]
−
t
r
s
[
l
c
a
]
+
t
l
s
[
l
e
a
f
u
[
b
+
1
]
]
−
t
l
s
[
l
c
a
]
t=trs[leafu[a-1]]-trs[lca]+tls[leafu[b+1]]-tls[lca]
t=trs[leafu[a−1]]−trs[lca]+tls[leafu[b+1]]−tls[lca]
∑
v
∈
S
[
l
,
r
]
d
[
v
]
\sum_{v\in S[l,r]}d[v]
∑v∈S[l,r]d[v] 满足
∑
v
∈
S
[
l
,
r
]
d
[
v
]
=
t
d
r
s
[
l
e
a
f
u
[
a
−
1
]
]
−
t
d
r
s
[
l
c
a
]
+
t
d
l
s
[
l
e
a
f
u
[
b
+
1
]
]
−
t
d
l
s
[
l
c
a
]
\sum_{v\in S[l,r]}d[v]=tdrs[leafu[a-1]]-tdrs[lca]+tdls[leafu[b+1]]-tdls[lca]
v∈S[l,r]∑d[v]=tdrs[leafu[a−1]]−tdrs[lca]+tdls[leafu[b+1]]−tdls[lca]似乎以上部分都是可以使用
O
(
l
o
g
)
O(log)
O(log) (寻找
l
c
a
lca
lca)来
O
(
1
)
O(1)
O(1) 地解决问题,但是
a
n
s
ans
ans 还有一个部分:
−
∑
v
∈
S
[
l
,
r
]
2
⋅
d
[
l
c
a
(
u
,
v
)
]
-\sum_{v\in S[l,r]}2\cdot d[lca(u,v)]
−v∈S[l,r]∑2⋅d[lca(u,v)]这个部分能否使用树上差分呢?
答案是:肯定不行。
为什么?因为
v
v
v 在改变时,
l
c
a
(
u
,
v
)
lca(u,v)
lca(u,v) 也在跟着改变。
那么怎么做?
分类讨论
u
u
u 的位置,这里就和我的暴力思路有点相像。
分以下几类:
先假设以
l
e
a
f
u
[
a
−
1
]
、
l
e
a
f
u
[
b
+
1
]
,
l
c
a
leafu[a-1]、leafu[b+1],lca
leafu[a−1]、leafu[b+1],lca 围成的树为
t
r
e
tre
tre。
令
f
l
c
a
(
)
flca()
flca() 为寻找
l
c
a
lca
lca 的算法。
-
u
u
u 在
t
r
e
tre
tre 之外时。这样又要分两类
- 当 u u u 是 l c a lca lca 的祖先,那么一定满足 ∀ v ∈ S [ l , r ] , f l c a ( u , v ) = u \forall v\in S[l,r],flca(u,v)=u ∀v∈S[l,r],flca(u,v)=u
- 当 u u u 不是 l c a lca lca 的祖先,那么一定满足 ∀ v ∈ S [ l , r ] , f l c a ( u , v ) = l c a ( l c a , u ) \forall v\in S[l,r],flca(u,v)=lca(lca,u) ∀v∈S[l,r],flca(u,v)=lca(lca,u) - u u u 在 t r e tre tre 之内时,对于这种情况需要自行推理。。。
另注:
可以发现,如果我们就这样建树的话,如果询问区间
[
1
,
n
]
[1,n]
[1,n] 之类的区间就会出现问题,那么怎么解决?其实可以这样建图(以题目的树为例)
代码
没时间补题啊