Day4 树的直径、重心以及基环树

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/87205755

A. POJ 1985 Cow Marathon 树的直径-模板

题目

POJ 1985

代码

树形DP求树的直径

#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
using namespace std;
const int maxn=4e4+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],edge[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y,int z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}
int d[maxn<<1],v[maxn<<1],ans;
inline void dp(int x)
{
	v[x]=1;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (v[y]) continue;
		dp(y);
		ans=max(ans,d[x]+d[y]+edge[i]);
		d[x]=max(d[x],d[y]+edge[i]);
	}
}
int main()
{
	int n,m;read(n);read(m);
	for (int i=1;i<=m;++i)
	{
		int x,y,z;char s;
		read(x);read(y);read(z);
		cin>>s;
		add(x,y,z),add(y,x,z);
	}
	dp(1);
	printf("%d\n",ans);
	return 0;
}

BFS 求树的直径

#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
using namespace std;
const int maxn=4e4+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],edge[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y,int z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}
int d[maxn<<1],v[maxn<<1],ans,n,m,point;
inline void bfs(int s)
{
	memset(v,0,sizeof(v));
	memset(d,0,sizeof(d));
	queue<int> q;
	v[s]=1;
	q.push(s);
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			if (!v[y])
			{
				v[y]=1;
				d[y]=d[x]+edge[i];
				q.push(y);
			}
		}
	}
	ans=0;
	for (int i=1;i<=n;++i)
		if (d[i]>ans)
		{
			ans=d[i];
			point=i;
		}
}
int main()
{
	read(n);read(m);
	for (int i=1;i<=m;++i)
	{
		int x,y,z;char s;
		read(x);read(y);read(z);
		cin>>s;
		add(x,y,z),add(y,x,z);
	}
	bfs(1),bfs(point);
	printf("%d\n",ans);
	return 0;
}

B. POJ 1849 Two 树的直径

题目

POJ 1849

代码

#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
using namespace std;
const int maxn=4e4+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],edge[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y,int z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}
int d[maxn<<1],v[maxn<<1],ans;
inline void dp(int x)
{
	v[x]=1;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (v[y]) continue;
		dp(y);
		ans=max(ans,d[x]+d[y]+edge[i]);
		d[x]=max(d[x],d[y]+edge[i]);
	}
}
int main()
{
	int n,m,sum=0;read(n);read(m);
	for (int i=1;i<n;++i)
	{
		int x,y,z;
		read(x);read(y);read(z);
		add(x,y,z),add(y,x,z);
		sum+=z;
	}
	dp(1);
	printf("%d\n",(sum<<1)-ans);
	return 0;
}

C. POJ 1655 Balancing Act 树的重心-模板

题目

POJ 1655

代码

#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
using namespace std;
const int maxn=2e4+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') f=-1, ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int vis[maxn],siz[maxn],ans,pos,n;
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
inline void dfs(int x)
{
    vis[x]=1,siz[x]=1;
    int maxpart=0;
    for (int i=head[x];i;i=Next[i])
    {
    	int y=ver[i];
    	if (!vis[y])
		{
            dfs(y);
            siz[x]+=siz[y];
            maxpart=max(maxpart,siz[y]);
        }
	}
    maxpart=max(maxpart,n-siz[x]);
    if (maxpart<ans || (maxpart==ans && x<pos))
		ans=maxpart,pos=x;
}
int main()
{
    int t;read(t);
    while (t--)
	{
        read(n);
        memset(vis,0,sizeof(vis));
		memset(head,0,sizeof(head));
        len=pos=0;
		ans=0x3f3f3f3f;
        for (int x,y,i=1;i<n;++i)
		{
            read(x);read(y);
            add(x,y);add(y,x);
		}
        dfs(1);
        printf("%d %d\n",pos,ans);
    }
    return 0;
}

来自鑫神的邻接矩阵写法

#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
using namespace std;
const int maxn=2e4+10;
const int INF=0x3f3f3f3f;
vector<int>G[maxn];
int son[maxn];
int ans,n,balance,t;

void dfs(int v,int fa)
{
	son[v]=1;
	int d=G[v].size();
	int pre_balance=0;
	for (int i=0;i<d;i++)
	{
		int k=G[v][i];
		if (k!=fa)
		{
			dfs(k,v);
			son[v]+=son[k];
			pre_balance=max(pre_balance,son[k]);
		}
	}
	pre_balance=max(pre_balance,n-son[v]);
	if (pre_balance<balance || (pre_balance==balance&&v<ans))
	{
		ans=v;
		balance=pre_balance;
	}
}
int main()
{
	cin>>t;
	while (t--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;++i)
			G[i].clear();
		for (int i=1;i<n;i++)
		{
			int s,e;
			scanf("%d%d",&s,&e);
			G[s].push_back(e);
			G[e].push_back(s);
		}
		memset(son,0,sizeof(son));
		ans=0;balance=INF;
		dfs(1,0);
		cout<<ans<<' '<<balance<<endl;
	}
	return 0;
}

NOIP 2018 旅行

题目

LUOGU 5022

题解

1.如果 m = = n 1 m==n-1 的话,这就是一棵普通的树,我们只要贪心选点(编号较小的优先遍历)进行dfs即可。
2.如果 m = = n m==n 的话,就是树多连了一条边,即基环树,那么求一个基环树的字典序最小的遍历序列该怎么办,经过老师的讲解,以及看了半天题解后,我们可以想到我们会求树的遍历序列,而基环树就是树多加一条边,那么我们就可以枚举删边,然后依照第一种情况来写,复杂度是O(n2)。
最后要说明的是:这份代码会T最后三个点。未来改进。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=5050;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
vector<int>G[maxn];
int ans[maxn],edge[maxn][2];
int n,m;
namespace solve1
{
	int cnt=0;
	bool vis[maxn];
	void dfs(int x,int fa)
	{
		ans[++cnt]=x;
		vis[x]=1;
		for (int i=0;i<G[x].size();++i)
		{
			int y=G[x][i];
			if (!vis[y])
				dfs(y,x);
		}
	}
	void main()
	{
		memset(vis,0,sizeof(vis));
		memset(ans,0,sizeof(ans));
		cnt=0;//注意初始化
		for (int i=1;i<=n;++i)
			sort(G[i].begin(),G[i].end());//贪心选点,先遍历编号小的点
		dfs(1,0);
		for (int i=1;i<=n;++i)
			printf("%d ",ans[i]);
	}
}
namespace solve2
{
	int cnt=0,res[maxn];
	bool vis[maxn];
	bool cmp()
	{
		for (int i=1;i<=n;++i)
			if (ans[i]!=res[i])
				return ans[i]>res[i];//得到的答案不是未连通图的情况
		return false;
	}
	int deletex,deletey;
	bool check(int x,int y)//避免这条边已经被删掉,然而又去遍历的情况
	{
		if ((x==deletex&&y==deletey) || (x==deletey&&y==deletex))
			return false;
		return true;
	}
	void dfs(int x,int fa)
	{
		vis[x]=1;
		res[++cnt]=x;
		for (int i=0;i<G[x].size();++i)
		{
			int y=G[x][i];
			if (!vis[y] && check(x,y))
				dfs(y,x);
		}
	}
	void main()
	{
		memset(ans,0x3f,sizeof(ans));
		memset(vis,0,sizeof(vis));
		for (int i=1;i<=n;++i)
            sort(G[i].begin(),G[i].end());//同上
		for (int i=1;i<=m;++i)
		{
			cnt=0;
			memset(res,0,sizeof(res));
			memset(vis,0,sizeof(vis));//初始化
			deletex=edge[i][0];
			deletey=edge[i][1];//枚举删边
			dfs(1,0);
			if (cmp()&&cnt==n)//已经统计出了答案且答案不是未连通图的情况
				memcpy(ans,res,sizeof(res));
		}
		for (int i=1;i<=n;++i)
			printf("%d ",ans[i]);
	}
}
int main()
{
	read(n);read(m);
	for (int i=1;i<=m;++i)
	{
		int x,y;
		read(x);read(y);
		G[x].push_back(y);
        G[y].push_back(x);
		edge[i][0]=x;//将要假删除的一条边
        edge[i][1]=y;
	}
	if (m==n-1)
		solve1::main();//如果是一棵树的话
	else
		solve2::main();//如果是基环树的话
	return 0;
}

D. BZOJ 1791: [IOI2008]Island 岛屿 基环树直径

题目

BZOJ 1791

代码

/**************************************************************
    Problem: 1791
    User: sjh
    Language: C++
    Result: Accepted
    Time:8908 ms
    Memory:95044 kb
****************************************************************/ 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int n,m,t;//t是标识符
int ver[maxn<<1],edge[maxn<<1],next[maxn<<1],head[maxn],tot,du[maxn];
void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z,next[tot]=head[x],head[x]=tot,++du[y];
}
int c[maxn];//环上的点
int v[maxn];
int q[maxn<<1];
void bfs(int s,int t)
{
    int l,r;
    q[l=r=1]=s;//手写队列维护
    c[s]=t;//标记连通块(看每个节点属于哪个基环树)
    while (l<=r)
    {
        for (int i=head[q[l]]; i; i=next[i])
            if (!c[ver[i]])
            {
                q[++r]=ver[i];
                c[ver[i]]=t;
            }
        l++;
    }
}
ll f[maxn];//每颗子树的直径
ll d[maxn];//每个节点的子树大小
void topsort()//找环操作顺便处理一种情况(直径不经过环)
{
    int l=1,r=0,x,y;
    for (int i=1; i<=n; ++i)
        if (du[i]==1)//无向图度数为1
            q[++r]=i;
    while (l<=r)
    {
        for (int i=head[x=q[l]]; i; i=next[i])
            if (du[y=ver[i]]>1)//度大于1可更新答案
            {
                d[c[x]]=max(d[c[x]],f[x]+f[y]+edge[i]);//子树内最长链
                f[y]=max(f[y],f[x]+edge[i]);//f[x]表示x子树中离x最远的点的距离
                if ((--du[y])==1)
                    q[++r]=y;
            }
        l++;
    }
}
ll a[maxn<<1];
ll b[maxn<<1];
void dp(int t,int x)
{
    int m=0,i,l=0,r,y=x;
    do
    {
        a[++m]=f[y];
        du[y]=1;
        for (i=head[y]; i; i=next[i])
            if (du[ver[i]]>1)//点在环上
            {
                y=ver[i];
                b[m+1]=b[m]+edge[i];//b[i]表示环上x到i的距离
                break;
            }
    } while (i);//此时答案为 f[i]+f[j]+dis[i][j]的最大值,dis[i][j]表示环上i到j的最远距离
    if (m==2)//跑到环外,需要特判
    {
        for (i=head[y]; i; i=next[i])
            if (ver[i]==x)
                l=max(l,edge[i]);
        d[t]=max(d[t],f[x]+f[y]+l);
        return;
    }
    for (i=head[y]; i; i=next[i])//连接环的首尾
        if (ver[i]==x)
        {
            b[m+1]=b[m]+edge[i];
            break;
        }
    for (i=1; i<m; ++i)//由于是环,所以复制一份
    {
        a[m+i]=a[i];
        b[m+i]=b[m+1]+b[i];
    }
    q[l=r=1]=1;
    for (i=2; i<2*m; ++i)
    {
        while (l<=r && i-q[l]>=m) ++l;
        d[t]=max(d[t],a[i]+a[q[l]]+b[i]-b[q[l]]);
        while (l<=r && a[q[r]]+b[i]-b[q[r]]<=a[i]) --r;//单调队列维护
        q[++r]=i;
    }
}
int main()
{
    read(n);
    for(int i=1; i<=n; ++i)
    {
        int x,y;
        read(x);read(y);
        add(i,x,y),add(x,i,y);
    }
    for (int i=1; i<=n; ++i)
        if (!c[i])
            bfs(i,++t);//统计有多少基环树
    topsort();//拓扑找环
 
    memset(v,0,sizeof(v));//重新利用v数组,当作基环树是否算过
    ll ans=0ll;
    for (int i=1; i<=n; ++i)
        if (du[i]>1 && !v[c[i]])//每个基环树只跑一遍并且此时i是环上一点
        {
            v[c[i]]=1;
            dp(c[i],i);//求基环树的直径
            ans+=d[c[i]];
        }
    printf("%lld\n",ans);
    return 0;
}

E. CF 835F Roads in the Kingdom 基环树-删边

题目

LUOGU CF 835F

代码

在这里插入代码片

F. BZOJ 1040: [ZJOI2008]骑士 基环树DP

题目

BZOJ 1040

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+5e2;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int n,power[maxn],hate[maxn],vis[maxn],U,V;
ll f[maxn],g[maxn];
inline void dfs(int x,int fa)//dfs找环
{
	vis[x]=1;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (y!=fa)
		{
			if (!vis[y]) dfs(y,x);
			else
			{
				vis[y]=1;
				U=x,V=y;
				return ;
			}
		}
	}
}
inline void tree_dp(int x,int fa,int rt,int ban)//ban 不选的点
{
	vis[x]=1;
	f[x]=power[x];
	g[x]=0;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (x==rt && i==ban) continue;
		if (y!=fa && y!=rt)
		{
			tree_dp(y,x,rt,ban);
			f[x]+=g[y];
			g[x]+=max(g[y],f[y]);
		}
	}
}
int main()
{
	read(n);
	for (int i=1,k;i<=n;++i)
	{
		read(power[i]);read(hate[i]);
		add(i,hate[i]),add(hate[i],i);
	}
	ll ans=0;
	for (int i=1;i<=n;++i)
		if (!vis[i])
		{
			dfs(i,-1);
			int banu,banv;
			for (int i=head[U];i;i=Next[i])
				if (ver[i]==V)
				{
					banu=i;
					break;
				}
			for (int i=head[V];i;i=Next[i])
				if (ver[i]==U)
				{
					banv=i;
					break;
				}
			tree_dp(U,-1,U,banu);//断环为链并将断开的两个点强制其中一个点为根且不选,做一次树形DP
			ll uans=g[U];
			tree_dp(V,-1,V,banv);//对另一个点做同样操作
			ll vans=g[V];
			ans+=max(uans,vans);//取两次结果最大值加入ans
		}
	printf("%lld\n",ans);
	return 0;
}

小结

今天的题目真是和江苏数学卷一样,前半部分送分,后半部分送命。各个省选难度以上,所以,后三道题写了代码的,其实都是抄题解的,不过也稍微理解了点,集训结束后,还要回来把这些题再好好写写。。。。话说从明天开始,基本上题题都是省选级别以上,什么Tanjan之类的,虽然之前看过,也写了一点,然而还是很懵,我后面该怎么过啊!

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/87205755
今日推荐