UVA1674 闪电的能量 Lightning Energy Report
problem:
给一个n个节点的树,点有点权(初始为0),在树上进行q次路径加,最后输出每个点的权值
data range:
(N,Q<=10^4)
solution:
可以不用树链剖分
考虑树上差分
设路径的两个端点为u,v,lca为u,v的最近公共祖先,f为lca的父亲
那么我们就可以在u,v上加上权值,在lca上减去权值,在f上再减去一次权值
手推下就好理解了,仅有路径上的点加且仅加了一倍权值
最后再dfs一遍统计答案就行了
Space time complexity:
时间:(O(nlogn))
空间:(O(nlogn))
code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+5,H=16;
int t,n,q,sign;
int tin[N],tout[N],fa[N][20],dep[N],val[N];
vector<int>e[N];
void dfs1(int u,int pr)
{
tin[u]=++sign;
dep[u]=dep[pr]+1;fa[u][0]=pr;
for(int i=1;(1<<i)<=dep[u];++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(size_t i=0;i<e[u].size();++i)
{
int v=e[u][i];
if(v==pr)continue;
dfs1(v,u);
}
tout[u]=++sign;
}
inline bool isac(int x,int y){return tin[x]<=tin[y]&&tout[y]<=tout[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(isac(x,y))return x;
for(int i=H;i>=0;--i)
if(!isac(fa[x][i],y))x=fa[x][i];
return fa[x][0];
}
void dfs2(int u,int pr)
{
for(size_t i=0;i<e[u].size();++i)
{
int v=e[u][i];
if(v==pr)continue;
dfs2(v,u);val[u]+=val[v];
}
}
int main()
{
int kase=0;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
fill(val+1,val+n+1,0);
for(int i=1;i<=n;++i)
fill(fa[i],fa[i]+20,0),e[i].clear();
for(int i=1;i<n;++i)
{
int u,v;scanf("%d%d",&u,&v);++u,++v;
e[u].push_back(v);
e[v].push_back(u);
}
sign=0;dfs1(1,0);tin[0]=0,tout[0]=++sign;
scanf("%d",&q);
while(q--)
{
int u,v,c;scanf("%d%d%d",&u,&v,&c);++u,++v;
int lcauv=lca(u,v);
val[u]+=c,val[v]+=c;
val[lcauv]-=c;
if(fa[lcauv][0])val[fa[lcauv][0]]-=c;
}
dfs2(1,0);
printf("Case #%d:
",++kase);
for(int i=1;i<=n;++i)printf("%d
",val[i]);
}
return 0;
}
Rikka与路径的交集(Rikka with Intersection of Paths)
problem:
给出一个n个节点的树以及树上m条路径,要求选择其中k条路径使得这k条路径至少有一个公共点。求选择的方案数
data range:
(N,M<=3*10^5)
(2<=K<=M)
solution:
这题是本次所有题目中最难的。上道题其实是本题的严格弱化(单就所用到的方法来说)
本题计数的难点在于如何做到不重不漏
首先我们定义一条路径的lca就是这条路径两个端点的lca
我们有这样一个神仙定理:
树上两条路径相交,那么交点必然有一个节点为两条路径中某一条的lca
我不会证明,但是可以感性理解
如果不满足以上定理,相交就会是这样的情况(红点为相交处)
显然不可能(树上每个节点(除根节点)都有唯一父亲,而图中红点有两个父亲)
接下来我们将这个定理运用到本题中来
枚举每个节点作为k条路径的交点,当且仅当这k条路径中至少有一条路径的lca为这个节点时我们才统计这种选择
- 为什么这样做不会遗漏?
因为每条路径都对应一个lca,因此不会统计漏
- 为什么这样做不会重复?
考虑某一种选择方案,不妨设其中至少一条路径的lca为u节点
那么这种方案当且仅当枚举到u时才会被统计到,因为每条路径都有且仅有一个lca
现在考虑怎么计算这个东西
假设我们已经对于每个节点u求出有(A_u)条路径以u为lca,有(P_u)条路径经过u节点
那么这个节点的贡献就是(C(P_u,k)-C(P_u-A_u,k))
意思就是所有情况减去不合法情况
对于(A_u),读入路径时求出lca累加即可
对于(P_u),完全同上一道题
那么此题就结束了
p.s.至于如何求组合数,我是先预处理阶乘以及阶乘的逆元然后就可以做到(O(1))查询
space time complexity:
时间:(O(nlogn))
空间:(O(nlogn))
code:
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,H=19,mod=1e9+7;
int n,t,m,k,sign,ans;
int dep[N],fa[N][20],tin[N],tout[N],a[N],p[N];
int fac[N],inv[N];
vector<int>e[N];
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
inline int qpow(int x,int y)
{
int ans=1;
for(;y;y>>=1,x=1ll*x*x%mod)
if(y&1)ans=1ll*ans*x%mod;
return ans;
}
inline void pre(int lim)
{
fac[0]=1;
for(int i=1;i<=lim;++i)fac[i]=1ll*fac[i-1]*i%mod;
inv[lim]=qpow(fac[lim],mod-2);
for(int i=lim-1;~i;--i)inv[i]=1ll*inv[i+1]*(i+1)%mod;
}
inline int C(int x,int y){return x<y?0:1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}
void dfs(int u,int pr=0)
{
tin[u]=++sign;
dep[u]=dep[pr]+1;fa[u][0]=pr;
for(int i=1;(1<<i)<=dep[u];++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(size_t i=0;i<e[u].size();++i)
{
int v=e[u][i];
if(v!=pr)dfs(v,u);
}
tout[u]=++sign;
}
inline bool isac(int x,int y){return tin[x]<=tin[y]&&tout[y]<=tout[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(isac(x,y))return x;
for(int i=H;~i;--i)
if(!isac(fa[x][i],y))x=fa[x][i];
return fa[x][0];
}
void calc(int u,int pr=0)
{
for(size_t i=0;i<e[u].size();++i)
{
int v=e[u][i];
if(v==pr)continue;
calc(v,u);p[u]+=p[v];
}
ans=add(ans,dec(C(p[u],k),C(p[u]-a[u],k)));
}
int main()
{
pre(N-5);
t=read();
while(t--)
{
n=read(),m=read(),k=read();
for(int i=1;i<=n;++i)
e[i].clear(),fill(fa[i],fa[i]+H+1,0);
for(int i=1;i<n;++i)
{
int x=read(),y=read();
e[x].push_back(y);
e[y].push_back(x);
}
sign=0;dfs(1);tin[0]=0,tout[0]=++sign;
fill(a+1,a+n+1,0),fill(p+1,p+n+1,0);
while(m--)
{
int x=read(),y=read();
int lcaxy=lca(x,y);
++a[lcaxy];
++p[x],++p[y],--p[lcaxy];
if(fa[lcaxy][0])--p[fa[lcaxy][0]];
}
ans=0;calc(1);
printf("%d
",ans);
}
return 0;
}
村庄有多远(How far away? HDU2586)
problem:
给出一颗n个节点的树,m个询问,每次询问树上两点的距离
data range:
(N<=4*10^4)
(M<=200)
solution:
lca模板题(甚至暴力跳lca也不会超时)
space time complexity:
时间&空间:(O(nlogn))
code:
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=4e4+5;
int t,m,n,h,tot,fa[N][20],d[N],dep[N];
int fi[N],ne[N<<1],to[N<<1],w[N<<1];
inline void add(int x,int y,int s)
{
ne[++tot]=fi[x],fi[x]=tot,to[tot]=y,w[tot]=s;
}
void dfs(int x,int pr)
{
dep[x]=dep[pr]+1;fa[x][0]=pr;
for(int i=1;(1<<i)<=dep[x];++i)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=fi[x];i;i=ne[i])
{
int v=to[i];
if(v==pr)continue;
d[v]=d[x]+w[i];
dfs(v,x);
}
}
inline int lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=h;i>=0;--i)
if(dep[fa[x][i]]>=dep[y])
x=fa[x][i];
if(x==y)return x;
for(int i=h;i>=0;--i)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline int dis(int x,int y)
{
return d[x]+d[y]-d[lca(x,y)]*2;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
tot=0;fill(fi+1,fi+n+1,0);
h=ceil(log2(n));
for(int i=1;i<n;++i)
{
int x,y,s;scanf("%d%d%d",&x,&y,&s);
add(x,y,s);add(y,x,s);
}
d[1]=dep[0]=0;
dfs(1,0);
while(m--)
{
int x,y;scanf("%d%d",&x,&y);
printf("%d
",dis(x,y));
}
}
return 0;
}
祖孙询问(LOJ10135)
problem:
已知一棵n个节点的有根树。有m个询问,每个询问给出了一对节点的编号x和y,询问x与y的祖孙关系。
data range:
(N,M<=4*10^4)
solution:
可以做到(O(n))预处理(O(1))查询
记下每个节点u进入时的时间(tin_u)和离开时的时间(tout_u)
x是y的祖先当且仅当满足tin[x]<tin[y]&&tout[y]<tout[x]
space time complexity:
时间&空间:(O(n))
code:
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+5;
vector<int>v[N];
int n,m,rt,timer,tin[N],tout[N];
void dfs(int x,int pr)
{
tin[x]=++timer;
for(int i=0;i<v[x].size();++i)
{
int u=v[x][i];
if(u==pr)continue;
dfs(u,x);
}
tout[x]=++timer;
}
inline bool is_ancestor(int x,int y)
{
return tin[x]<tin[y]&&tout[y]<tout[x];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
int a,b;scanf("%d%d",&a,&b);
if(b==-1){rt=a;continue;}
v[a].push_back(b);
v[b].push_back(a);
}
dfs(rt,-1);
scanf("%d",&m);
while(m--)
{
int a,b;scanf("%d%d",&a,&b);
if(is_ancestor(a,b))puts("1");
else if(is_ancestor(b,a))puts("2");
else puts("0");
}
return 0;
}
Network(POJ3417)
problem:
一棵有n个节点的无根树,再给出m条边,把这m条边连上,每次你能毁掉两条边,规定一条是树边,一条新边。问有多少种方案能使树断裂。
data range:
(N,M<=10^5)
solution:
每加入一条新边,树上就会多一个简单环
对于一条树边,如果它属于这个环,那么我们就称它被所加入的新边覆盖
枚举每条树边,考虑删除这条边
如果这条边没有被新边覆盖,那么删去这条边后再任意删一条新边都满足条件
如果这条边仅被一条新边覆盖,那么删去这条边后只能删去覆盖它的新边才能满足条件
如果这条边被多条新边覆盖,那么这条树边对答案没有贡献
space time complexity:
时间&空间:(O(nlogn))
code:
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<math.h>
#include<cctype>
using namespace std;
const int N=1e5+5;
int n,m,tot,timer,ans,h,fa[N][20],tin[N],tout[N],dep[N];
int fi[N],ne[N<<1],to[N<<1],val[N];
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
inline void add(int x,int y){ne[++tot]=fi[x],fi[x]=tot,to[tot]=y;}
void dfs(int x,int pr)
{
tin[x]=++timer;
dep[x]=dep[pr]+1;fa[x][0]=pr;
for(int i=1;i<=h;++i)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=fi[x];i;i=ne[i])
{
int v=to[i];
if(v==pr)continue;
dfs(v,x);
}
tout[x]=++timer;
}
inline bool anc(int x,int y){return tin[x]<tin[y]&&tout[y]<tout[x];}
inline int lca(int x,int y)
{
if(x==y)return x;
if(dep[x]>dep[y])swap(x,y);
if(anc(x,y))return x;
for(int i=h;i>=0;--i)
if(!anc(fa[x][i],y))x=fa[x][i];
return fa[x][0];
}
void dfs2(int x,int pr)
{
for(int i=fi[x];i;i=ne[i])
{
int v=to[i];
if(v==pr)continue;
dfs2(v,x);
val[x]+=val[v];
}
if(val[x]==1)++ans;
else if(!val[x])ans+=m;
}
int main()
{
n=read(),m=read();h=(int)(log(n)/log(2))+1;
for(int i=1;i<n;++i)
{
int x=read(),y=read();
add(x,y);add(y,x);
}
for(int i=0;i<=h;++i)fa[1][i]=1;
dfs(1,1);
for(int i=1;i<=m;++i)
{
int x=read(),y=read();
++val[x],++val[y],val[lca(x,y)]-=2;
}
dfs2(1,1);
printf("%d
",ans-m);
return 0;
}
聚会(AHOI 2008)
problem:
大小为N的一棵树上给定3个点A/B/C,找出一个点离这3个点距离之和最小(询问M次)
data range:
(N,M<=5*10^5)
solution:
对这三个点两两求lca
容易发现三个lca中有两个都是相同的
各种画图手玩打表证明不重合的公共点才是最优解
输出答案时可以巧妙地使用异或操作
space time complexity:
时间&空间:(O(nlogn))
code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m,tot,tim;
int fi[N],ne[N<<1],to[N<<1],dep[N],f[N][20],tin[N],tout[N];
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
inline void add(int x,int y){ne[++tot]=fi[x],fi[x]=tot,to[tot]=y;}
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;f[u][0]=fa;
for(int i=1;(1<<i)<=dep[u];++i)
f[u][i]=f[f[u][i-1]][i-1];
tin[u]=++tim;
for(int i=fi[u];i;i=ne[i])
{
int v=to[i];
if(v==fa)continue;
dfs(v,u);
}
tout[u]=++tim;
}
inline bool is_Anc(int x,int y){return tin[x]<=tin[y]&&tout[y]<=tout[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(is_Anc(x,y))return x;
int h=ceil(log2(dep[x]));
for(int i=h;i>=0;--i)
if(!is_Anc(f[x][i],y))
x=f[x][i];
return f[x][0];
}
inline int dis(int x,int y,int Lca){return dep[x]+dep[y]-2*dep[Lca];}
int main()
{
n=read(),m=read();
for(int i=1;i<n;++i)
{
int x=read(),y=read();
add(x,y);add(y,x);
}
dfs(1,0);tin[0]=0,tout[0]=++tim;
while(m--)
{
int x=read(),y=read(),z=read();
int lca1=lca(x,y),lca2=lca(x,z),lca3=lca(y,z);
int d=(dis(x,y,lca1)+dis(x,z,lca2)+dis(y,z,lca3))>>1;
printf("%d %d
",lca1^lca2^lca3,d);
}
return 0;
}
异象石(SDOI2015)
problem:
大小为N的一棵树,M个时刻发生M个事件,类型有三:
1.某个点出现异象石。
2.删除异象石。
3.询问问异象石所在的点连通边集的总长度最小是多少。
data range:
(N<=10^5)
solution:
此题思路很妙啊
考虑按照dfs序维护所有异象石,设sum为相邻两个异象石的距离之和(首尾也算),答案就是sum>>1
具体来说
- 插入x
sum减去dis(pre,nxt),再加上dis(pre,x)+dis(x,nxt)
- 删除x
sum减去dis(pre,x)+dis(x,nxt),再加上dis(pre,nxt)
具体维护用set就可以了,但要特殊处理首尾相邻的情况
这道题还用到了不少关于set的技巧,值得积累
space time complexity:
时间&空间:(O(nlogn))
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5,H=17;
int n,dfn[N],dfc,sign,fa[N][20],dep[N];
ll len[N];
int tin[N],tout[N];
struct edge{int to,w;edge(int _to=0,int _w=0){to=_to,w=_w;}};
struct cmp{bool operator()(const int&x,const int&y){return dfn[x]<dfn[y];}};
set<int,cmp>s;
vector<edge>e[N];
void dfs(int u,int pr)
{
tin[u]=++sign,dfn[u]=++dfc;
fa[u][0]=pr,dep[u]=dep[pr]+1;
for(int i=1;(1<<i)<=dep[u];++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(size_t i=0;i<e[u].size();++i)
{
edge v=e[u][i];
if(v.to==pr)continue;
len[v.to]=len[u]+1ll*v.w;
dfs(v.to,u);
}
tout[u]=++sign;
}
inline bool isac(int x,int y){return tin[x]<=tin[y]&&tout[y]<=tout[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(isac(x,y))return x;
for(int i=H;i>=0;--i)
if(!isac(fa[x][i],y))
x=fa[x][i];
return fa[x][0];
}
inline ll dis(int x,int y)
{
int LCA=lca(x,y);
return len[x]+len[y]-2*len[LCA];
}
inline int pre(int x)
{
set<int,cmp>::iterator it=s.lower_bound(x);
if(it==s.begin())return *s.rbegin();
it--;return *it;
}
inline int nxt(int x)
{
set<int,cmp>::iterator it=s.lower_bound(x);
it++;if(it==s.end())return *s.begin();
return *it;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;++i)
{
int x,y,z;scanf("%d%d%d",&x,&y,&z);
e[x].push_back(edge(y,z));
e[y].push_back(edge(x,z));
}
len[1]=0;dfs(1,0);tin[0]=0,tout[0]=++sign;
int m;scanf("%d",&m);
char ch[5];int x;ll ans=0;
while(m--)
{
scanf("%s",ch);
if(ch[0]=='?')printf("%lld
",ans>>1);
else
{
scanf("%d",&x);
if(ch[0]=='+')
{
s.insert(x);
int l=pre(x),r=nxt(x);
ans-=dis(l,r);
ans+=dis(l,x)+dis(x,r);
}
else
{
int l=pre(x),r=nxt(x);
ans-=dis(l,x)+dis(x,r);
ans+=dis(l,r);
s.erase(x);
}
}
}
return 0;
}