海亮Day6:差分及树上差分思想

又是有意思的一天,在一上午大神的讲解下,我这只蒟蒻瑟瑟发抖的听完了几乎挂完的课,原想不总结了,结果是源神的题目讲解让我对刷题产生了新的概念!!!

T1 :Max Flow

[BZOj 4390 ] [Usaco2015 dec]
良心的我给出了简化题目:

给定一棵有N个点的树,所有节点的权值都为0。有K次操作,每次指定两个点s,t,将s到t路径上所有点的权值都加一。请输出K次操作完毕后权值最大的那个点的权值。

由于是上午刚听完课,看到板子题就很激动,上去就敲出了LCA,倍增,预处理这样一些东西,也并不是很困难,但是但是但是,还是卡了我半个小时,在源神的单点 查询下,我这家伙竟然又又又又开小数组了这个习惯好像改不好了
跳过这一段!!
就是一个板子题,不是多难,直接上代码。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int N=500003;
int n,m,kkk=21,ans=0;
int tot=0,f[N][30];//就是这个傻逼数组Wa了我半个小时
int d[N],last[N],sum[N];
queue<int>q;
struct see
{
	int next,ver,edge;
}e[1000001];
inline void add(int x,int y){e[++tot].ver=y;e[tot].next=last[x];last[x]=tot;}
inline void bfs(int x)//预处理
{
	q.push(x); d[1]=1;
	while (!q.empty())
	{
		int x=q.front(); q.pop();
		for (int i=last[x];i;i=e[i].next)
		{
			int y=e[i].ver;
			if (d[y]) continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			for (int j=1;j<=kkk;++j)
			f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}
inline int lca(int x,int y)//LCA倍增
{
	if (d[x]>d[y]) swap(x,y);
	for (int i=kkk;i>=0;--i)
	if (d[f[y][i]]>=d[x]) y=f[y][i];
	for (int i=kkk;i>=0;--i)
	if (f[x][i]!=f[y][i])
	x=f[x][i],y=f[y][i];
	if (x==y) return x;
	return f[x][0];
}
inline void diff(int x,int y)//差分
{	
	for(int i=last[x];i;i=e[i].next)
	{
		if(e[i].ver==y) continue;
		diff(e[i].ver,x);
		sum[x]+=sum[e[i].ver]; 
	}
	ans = max(sum[x],ans);
}
int main()
{
	memset(last,0,sizeof(last));
	memset(f,0,sizeof(f));
	memset(d,0,sizeof(d));
	memset(sum,0,sizeof(sum));
	cin>>n>>m;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y); add(y,x);
	}
	bfs(1);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		sum[a]++;sum[b]++;
		sum[lca(a,b)]--;
		sum[f[lca(a,b)][0]]--;
		//处理子节点和父亲节点以及父亲节点的父亲节点
	}
	diff(1,0);
	cout<<ans<<endl;
    return 0;
} 

T1就很愉快 的结束了,来到了T2(很水的过渡句)。

T2:Network

POJ 3417
(不知道为什么会有好多POJ的题??)
良心的我再一次给出了简化题目:

一棵有N个点的树,再往里面加入M条新边,现在要破坏其中的两条边,要求一条是原来树中的边,一条是新边,使其不连通。求方案的数量。(1 ≤ N ≤ 100000), 1 ≤ M ≤ 100000)。

一道四个小时的题目(知道我有多菜了吧>_<),一小时看题,一小时讲解,一小时领悟,一小时打代码!
接下来,就是激动人心的题解了!!
尽管是在源神的讲解下,但我也几乎领会完了。
细心读题就会发现,他让删两条边,一条主要边(树上原有的边),一条附加变(题目新加的边),首先画个图:
V
在这个图中的1–2是新边,(很自然的应该知道树是n-1条边,而图是n条边)这个时候,我们是一定要删1–2的新边,再删环上的任意一条边都可以,此时环就变成了一块一块的满足了题目要求,而这时候ans=;

这时候就会想到为什么不删1–5呢??
这就距离正解不远了!!!
还是画图:
在这里插入图片描述

在这个图中,想象一下,当1–5是一条附加边时,1–5连的边就是一条非非非常重要的附加边,这条边只有一个孩子,当断开他,你就可以为所欲为了 删掉任意一条边了!是不是很兴奋!

当你在兴奋地想蹦起来的时候,其实你忘记了第一种情况特殊情况!!

上图:
在这里插入图片描述
这个时候你还想要删1–4吗?? 他已经不是那个曾经的他了,醒醒!删他已经没有什么用了!

两种情况总结下来(最后一种是第一种的特殊情况),是不是明白了什么,是不是有迷茫了什么,想说,这怎么写代码呢?

for(int i=2;i<=n;i++)
	{
		if(sum[i]==1) ans++;//当标记为1时,为第一种情况
		if(sum[i]==0) ans+=m;//当标记为2时,为第二种情况
	}

是不是又蒙了,sum[i]是什么?
差分啊,在树上的操作,差分是重要思想,单点修改,区间查询的优秀算法.
差分出经过此点的次数,在进行判断ok了。

完整代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100003;
int n,m,kkk=21,ans=0;
int tot=0,f[N][30],d[N],last[N],sum[N];
queue<int>q;
struct see
{
	int next,ver,edge;
}e[200001];
inline void add(int x,int y){e[++tot].ver=y;e[tot].next=last[x];last[x]=tot;}
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
inline void bfs(int x)
{
	q.push(x); d[1]=1;
	while (!q.empty())
	{
		int x=q.front(); q.pop();
		for (int i=last[x];i;i=e[i].next)
		{
			int y=e[i].ver;
			if (d[y]) continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			for (int j=1;j<=kkk;++j)
			f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}
inline int lca(int x,int y)
{
	if (d[x]>d[y]) swap(x,y);
	for (int i=kkk;i>=0;--i)
	if (d[f[y][i]]>=d[x]) y=f[y][i];
	for (int i=kkk;i>=0;--i)
	if (f[x][i]!=f[y][i])
	x=f[x][i],y=f[y][i];
	if (x==y) return x;
	return f[x][0];
}
inline void diff(int x,int y)
{	
	for(int i=last[x];i;i=e[i].next)
	{
		if(e[i].ver==y) continue;
		diff(e[i].ver,x);
		sum[x]+=sum[e[i].ver]; 
	}
	
}
int main()
{
	memset(last,0,sizeof(last));
	memset(f,0,sizeof(f));
	memset(d,0,sizeof(d));
	memset(sum,0,sizeof(sum));
	n=read(); m=read();
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y); add(y,x);
	}
	bfs(1);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		a=read(); b=read();
		sum[a]++;sum[b]++;
		sum[lca(a,b)]-=2;
	}
	diff(1,0);
	for(int i=2;i<=n;i++)
	{
		if(sum[i]==1) ans++;
		if(sum[i]==0) ans+=m;
	}
	cout<<ans<<endl;
    return 0;
} 

好愉快哦 ,又A掉了一道题,但四个小时还是有点不值得的,但不过收获不小,凭自己对题目的理解,深入去思考出来的题目不多,但如果每一道题都是这样去写,那这样的收获岂不是比水过更好??重要的还是自己的理解!

废话不多说了,下一道(仍然是很水的过渡句)

T3:Alyona and a tree

CF 739B
良心的我又一次给出了简化题目:

一棵根节点为1的树,每个点都有点值a[i],每条边也有权值,
dist(v, u)表示从v到u边权和。现在给出“控制”的定义:对一个点u,
设点v在其子树上,且dis(u, v) ≤ a.v,则称u控制v。要求求出每个点控制了多少个点。
1 ≤ n ≤ 2 × 105, 1 ≤ a.i ≤ 109。

一看到dis(u, v) ≤ a.v,是不是就想到了单调性?一看到单调性就想到了二分||倍增,看到要区间修改就想到了差分。
好的,您一秒正解了!!!
差分查询,二分或倍增查找控制距离,并不是很困难。
OK,粘代码(快下课了,两个小时一篇博客,快死了0^0)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int N=2e5+10;
int n,m,kkk=21,ans[N],tot=0,f[N][30],last[N],s[N];
long long d[N];
struct see
{
	int next,ver,edge;
}e[N<<1];
inline int read ()
{
	int s=0,w=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
inline void add(int x,int y,int z){e[++tot].ver=y;e[tot].edge=z;e[tot].next=last[x];last[x]=tot;}
inline void dfs(int x,int fa,int dis)
{
	d[x]=d[fa]+dis, f[x][0]=fa;
	for(int i=1;i<=kkk;i++)
	f[x][i]=f[f[x][i-1]][i-1];
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].ver,z=e[i].edge;
		if(y==fa) continue;
		dfs(y,x,z);
		int hh=s[y],k=1,mld=y;
		while(k&&mld)
		{
			if(d[mld]-d[f[mld][k]]<=hh)
			{
				hh-=d[mld]-d[f[mld][k]];
				mld=f[mld][k];
				k<<=1;
			}
			else k>>=1;
		}//显然的倍增
		if(d[mld]-d[f[mld][0]]<=hh) mld=f[mld][0];
		--ans[f[mld][0]],++ans[f[y][0]];
	}
	ans[fa]+=ans[x];//显然的差分
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++) 
	s[i]=read();
	for (int i=2;i<=n;++i)
	{
		int x,z;
		x=read();z=read();
		add(i,x,z);add(x,i,z);//比较难以理解的输入
	}
	dfs(1,0,0);
	for(int i=1;i<=n;i++)
	cout<<ans[i]<<' ';cout<<endl;
    return 0;
} 

AK,AK,AK!!!(尽管有60+%的人都AK了)
经过今天的学习和刷题,总结今天的收获,感觉收获满满。

没有弯道超车,没有瞬间记忆,只有认认真真的,老老实实的去理解,去领悟,学习中的点点滴滴 。 ——blng

(再次%%%源神!!!)

猜你喜欢

转载自blog.csdn.net/secretzyl/article/details/87387074
今日推荐