备战noip week4

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:\n",++kase);
		for(int i=1;i<=n;++i)printf("%d\n",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\n",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\n",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\n",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\n",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\n",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;
}
posted @ 2020-09-25 19:40  BILL666  阅读(170)  评论(0编辑  收藏  举报