把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4757】[CERC2014] Parades(DP)

点此看题面

  • 一棵\(n\)个点的树,满足每个点的度数小于等于\(10\)
  • 给定\(m\)条树上路径,求最多从中选出多少条路径,满足任意两条路径无重边。
  • \(n\le10^3,m\le \frac {n(n-1)}2\)

初步\(DP\)思路

显然每棵子树最多向外连出一条路径,而能向外连出一条路径只需满足对应端点到该子树根路径上的边都未被选择。

因此,容易想到设\(f_{x,i}\)表示满足\(i\)\(x\)路径上的边都未被选择时,最多能在\(x\)的子树内选出多少条路径。

状压\(DP\)合并子节点

这题的一个良心性质就是每个点度数小于等于\(10\)

考虑要合并\(x\)的子节点信息时,我们先根据\(LCA\)\(x\)的所有路径,预处理出\(A_i\)表示第\(i\)个子节点单独能产生的最大贡献,\(B_{i,j}\)表示第\(i\)个和第\(j\)个子节点配对能产生的最大贡献。

然后我们设\(g_S\)表示集合\(S\)中的子节点能产生的最大贡献,转移只需考虑新加入单独一个点还是一对点。

预处理出\(g\)之后再要求\(f_{x,i}\)就很简单了,假设\(i\)\(x\)的子节点\(u\)的子树中,记\(S'\)为全集除去\(u\)之后的集合,则\(f_{x,i}=g_{S'}+f_{u,i}\)

代码:\(O(n2^kk^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 1000
#define K 10 
#define LN 10
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];vector<pair<int,int> > V[N+5];
int D[N+5],fa[N+5][LN+1];I void dfs(CI x)//预处理倍增数组
{
	RI i;for(i=1;i<=LN;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
	for(i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x][0]&&(D[e[i].to]=D[fa[e[i].to][0]=x]+1,dfs(e[i].to),0);
}
I int Jump(RI x,CI d)//求出x深度为d的祖先
{
	for(RI i=0;D[x]^d;++i) (D[x]^d)>>i&1&&(x=fa[x][i]);return x;
}
I int LCA(RI x,RI y)
{
	if(D[x]<D[y]&&(swap(x,y),0),(x=Jump(x,D[y]))==y) return x;
	for(RI i=LN;~i;--i) fa[x][i]^fa[y][i]&&(x=fa[x][i],y=fa[y][i]);return fa[x][0];
}
int f[N+5][N+5],q[K+5],rk[N+5],g[1<<K],A[K+5],B[K+5][K+5];I void DP(CI x)//动态规划
{
	RI i,j;for(i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x][0]&&(DP(e[i].to),0);//先DP完所有子节点
	RI t=0;for(i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x][0]&&(q[rk[e[i].to]=t++]=e[i].to);//记录子节点
	for(i=0;i^t;++i) for(A[i]=f[q[i]][q[i]],j=0;j^t;++j) B[i][j]=0;//初始化A,B
	RI u,v;for(vector<pair<int,int> >::iterator it=V[x].begin();it!=V[x].end();++it)//枚举所有以x为LCA的路径
	{
		if(it->second==x&&(swap(it->first,it->second),0),it->first==x)//一端是x
			{u=Jump(it->second,D[x]+1),Gmax(A[rk[u]],f[u][it->second]+1);continue;}//更新A
		u=Jump(it->first,D[x]+1),v=Jump(it->second,D[x]+1),rk[u]>rk[v]&&(swap(u,v),swap(it->first,it->second),0);
		Gmax(B[rk[u]][rk[v]],f[u][it->first]+f[v][it->second]+1);//更新B
	}
	for(i=0;i^(1<<t);++i) g[i]=0;for(i=0;i^(1<<t);++i)//状压DP求g
	{
		for(u=0;u^t;++u) !(i>>u&1)&&Gmax(g[i|(1<<u)],g[i]+A[u]);//加入单独一个点
		for(u=0;u^t;++u) if(!(i>>u&1)) for(v=u+1;v^t;++v) !(i>>v&1)&&Gmax(g[i|(1<<u)|(1<<v)],g[i]+B[u][v]);//加入一对点
	}
	for(i=1;i<=n;++i) f[x][i]=x^i?(D[x]<D[i]&&fa[u=Jump(i,D[x]+1)][0]==x?g[((1<<t)-1)^(1<<rk[u])]+f[u][i]:0):g[(1<<t)-1];//利用g求f
}
int main()
{
	RI Tt,i,x,y;scanf("%d",&Tt);W(Tt--)
	{
		for(scanf("%d",&n),ee=0,i=1;i<=n;++i) lnk[i]=0,V[i].clear();//注意清空
		for(i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
		for(dfs(1),scanf("%d",&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),V[LCA(x,y)].push_back(make_pair(x,y));//把每条路径都存到LCA上
		DP(1),printf("%d\n",f[1][1]);
	}return 0;
}
posted @ 2021-07-15 17:57  TheLostWeak  阅读(69)  评论(0编辑  收藏  举报