JieJie的学习记录--树状dp

树状DP主要母问题:

1.子树的大小
2.树的平衡点(重心)
3树的最大独立集,树的最小覆盖点,树的最小支配集
4.树的直径

1.子树的大小

例题描述:给定一个大小为n的树,求以i为根的子树大小。
1.设F[i] 为以i为根的子树大小
2.状态转移:F[i]=Σ(1,k)f[k]
代码如下:

#include
using namespace std;
int n;
int f[1001];
vector<int>e[1001]; 
void dfs(int x,int fa)// x为当前的节点,fa为其父亲 
{
    for(int i=0;i<e[x].size();i++)
	{
      int k=e[x][i];
	  if(k==fa)
	    continue;//不为上次 
	  dfs(k,x);
	  f[x]+=f[k];		
	}	
	f[x]++;//加上自身 
}
int main()
{
	cin>>n;
	int x,y;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
	    e[x].push_back(y);
		e[y].push_back(x);	
	} 
	int i;
	dfs(1,-1);
	while(cin>>i)
	{
		cout<<f[i]-1<<endl;//除开自己 
	}
	return 0;
	
}

2.树的平衡点

给你一个有 n 个点的树,求树的平衡点和删除平衡点后最大子树的节点数。所谓平衡点,指的是树中的一个点,删掉该点,使剩下的若干个连通块中,最大的连通块的块大小最少

1.设F[i]为以i为根的最大连通块
2.F[i]=max(n-F[i],max(F[k]+1))
3.balance = min(balance,F[i])
代码如下

#include
using namespace std;
int n;
vector<int>e[1001];
int f[1001];
int balance=10001,pos=0;
void dfs(int x,int fa)
{
	
	int maxn=0;
	f[x]=1;
	for(int i=0;i<e[x].size();i++)
	{
		int k=e[x][i];
		if(k==fa)
		  continue;
		dfs(k,x);
	    f[x]+=f[k];
	    maxn=max(maxn,f[k]);
	}
	maxn=max(f[x],n-f[x]);
	if(balance>maxn)
	{
		balance=maxn;
		pos=x;
	}
}
int main()
{
	cin>>n;
	int x,y;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,-1);
	cout<<balance-1<<endl<<pos;
	return 0;
}

3.1 树的最大独立集:

问题描述:有 n 名职员,编号为 1∼n ,他们的关系就像一棵以老板为根的树,父节点就是子节点的直接上司。每个职员有一个快乐指数,用整数 Hi 给出,现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。(没有上司的舞会)
1.对于F[i]有2种情况,F[i][0]为选取该点,F[i][1]为不选取该点
2.则F[i][0]+=max(F[k][1],F[k][0]);
3.F[i][1]+=F[k][0];
4.最后的答案即为max(F[1][0],F[1][1]) (从(1,-1)开始dfs)
代码如下:

#include
using namespace std;
const int N=6011;
int h[N];
int n;
int f[N][2]; 
vector<int>e[N];
void dfs(int x,int fa)
{
	f[x][1]=h[x];
	f[x][0]=0;
	for(int i=0;i<e[x].size();i++)
	{
		int k=e[x][i];
		if(k==fa)
		  continue;
		dfs(k,x);
		f[x][1]+=f[k][0];
		f[x][0]+=max(f[k][1],f[k][0]);
	}
	
}
int main()
{
	int x,y;
	cin>>n;
	for(int i=1;i<=n;i++)
	  cin>>h[i];
	for(int i=1;i<n;i++)
  {   
	cin>>x>>y;
	e[x].push_back(y);
	e[y].push_back(x);
  }
  cin>>x>>y;
  dfs(1,-1);
  cout<<max(f[1][1],f[1][0]);
  return 0;
	
}

3.2 树的最小覆盖点问题

问题描述:给你一个有 n 个点的树,每两个点之间至多只有一条边。如果在一个结点上放一个士兵,那他能看守与之相连的边,问最少放多少个兵,才能把所有的边能看守住。

1.设F[i],F[i]有2种情况:①:F[i][0]为选取该点的最小放置守卫数量
②:F[i][0]为不选取该点的最小放置守卫数量
2.状态转移:①:F[i][1]+=min(F[k][1],F[k][0]);
②:F[i][0]+=F[k][1];
3.ans=min(F[i][0],F[i][1]);

代码如下:

#include
using namespace std;
int n; 
int f[1001][2];
vector<int>e[1001];
void dfs(int x,int fa)
{
	f[x][1]=1;
	f[x][0]=0;
	for(int i=0;i<e[x].size();i++)
	{
		int k=e[x][i];
		if(k==fa)
		  continue;
		dfs(k,x);
		f[x][1]+=min(f[k][0],f[k][1]);
		f[x][0]+=f[k][1];
	}
}
int main()
{
	cin>>n;
	int x,y;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,-1);
	int ans=min(f[1][1],f[1][0]);
	cout<<ans;
	return 0;
}

3.3 树的最小支配集

**问题描述:**给你一个有 n 个点的树,每两个点之间至多只有一条边。如果在第 i 个点部署信号塔,就可以让它和所有与它相连的点都收到信号。求最少部署多少个信号塔能让所有点都能收到信号。

这道题中,一个节点不仅会影响其子节点,还会影响其父节点
1.设F[i],则有3种情况:
①:F[i][0]为选取该点的最小数量
②:F[i][1]为不选取该点,并且其连接的子节点会选的最小数量
③:F[i][2]为不选取该点,与其连接子节点也不选的最小数量
2.状态转移:
①:F[i][0]+=min(F[k][0],F[k][1],F[k][2]);
②:该情况为其子节点至少有一个选取,可以贪心一下,当存在F[k][1]>=F[k][0],即存在一个节点,不选它比选它更糟糕,则我们就选择它;另外,当不存在时,则选取min(F[k][0]);
③:F[i][2]+=min(F[k][1],F[k][0]);
代码如下:

#include
using namespace std;
int n;
vector<int>e[1001];
int f[1001][3];
void dfs(int x,int fa)
{
	f[x][0]=1;
	int minn=1001;
	bool flag=1; 
	for(int i=0;i<e[x].size();i++)
	{
		int k=e[x][i];
		if(k==fa)
		  continue;
		dfs(k,x);
		f[x][0]+=min(f[k][0],min(f[k][1],f[k][2]));
		f[x][2]+=min(f[k][0],f[k][1]);
		if(f[k][1]>=f[k][0])
		{
			f[x][1]+=f[k][0];
			flag=0;
	    }
	    else
		{
		f[x][1]+=f[k][1];
		 minn=min(minn,f[k][0]);	
		}    
		
    }
    if(flag)
    {
    	f[x][1]+=minn;
	}
}
int main()
{
	cin>>n;
	int x,y;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,-1);
    cout<<min(f[1][0],f[1][2]);
	return 0;
}

4.树的直径:

问题描述:
给你一个有 n 个点的树,树上的每个点有一个权值。定义一棵树的子链大小为:这个子链上所有节点的权值和。求这棵树上最大子链的结点权值和。

1.设f[i]为以i为根节点的最大连通块,g[i]为以i为根节点的次最大连通块
2.状态转移:若其子节点为根的最大连通块f[k]+1>f[i],则把大的赋给f[i],次大的赋给g[i]; 否则,若f[k]+1>g[i],则再次更新g[i];
3.ans=max(f[i]+g[i]+1,ans);
代码如下:

#include
using namespace std;
int n;
int f[1001];
int g[1001];
	int ans=0;
vector<int>e[1001];
void dfs(int x,int fa)
{

	for(int i=0;i<e[x].size();i++)
	{
		int k=e[x][i];
		if(k==fa)
		  continue;
		dfs(k,x);
		int t=f[k]+1;
		if(t>f[x])
		{
			g[x]=f[x];
			f[x]=t;
		}
		else if(t>g[x])
		{
			g[x]=t;
		} 
     }   
}
int main()
{cin>>n;

	int x,y;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,-1);
	for(int i=1;i<=n;i++)
	{
		cout<<i<<' '<<f[i]<<' '<<g[i]<<endl;
		ans=max(ans,f[i]+g[i]);
	}
	cout<<ans+1;//加上自身
	return 0;
} 

mood:JieJie的学习记录--树状dp_第1张图片

你可能感兴趣的:(JieJie的学习记录,学习,c++,算法)