zoukankan      html  css  js  c++  java
  • 备战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:
    ",++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;
    }
    
  • 相关阅读:
    2019-2020 20175207- 20175235 实验四 外设驱动程序设计
    2019-2020 20175207- 20175235 实验三 实时系统
    2019-2020 20175207- 20175235 实验二 固件程序设计
    2019-2020-1 20175207 20175235 实验一开发环境的熟悉
    2018-2019-2 20175235 实验五《网络编程与安全》实验报告
    2018-2019-2 20175235 实验四《Android开发基础》实验报告
    2018-2019-2 20175235 实验三《敏捷开发与XP实践》实验报告
    2018-2019-2 20175235 实验二《Java面向对象程序设计》实验报告
    第六周学习总结
    第五周学习总结
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/13732060.html
Copyright © 2011-2022 走看看