[CSP-S模拟测试]:点亮(状压DP+树上背包DP)

题目传送门(内部题121)


输入格式

  第一行,一个正整数$n$。
  第二行,$n-1$个正整数$p_2,p_3,...,p_n$。保证$p_u$是在$1$到$u-1$中等概率随机选取的。
  接下来$n$行,第$u$行有$2(n-1)$个数,分别为$a[u][1],b[u][1],...,a[u][u−1],b[u][u-1],a[u][u+1],b[u][u+1],...,a[u][n],b[u][n]$(即去掉$v=u$后的$n-1$对)。
  读入量较大,建议使用读入优化。


输出格式

  一个整数表示能得到整棵树的美丽度的最大值。


样例

样例输入1:

2
1
-71 69
100 -47

样例输出1:

69

样例输入2:

3
1 1
-32 19 84 21
-20 0 7 -86
-37 -33 16 -66

样例输出2:

39


数据范围与提示

样例$1$解释:

  最优方案是点亮$1$不点亮$2$,此时$(1,2)$的贡献为$69$,$(2,1)$没有贡献。

样例$2$解释:

  最优方案是不点亮$1,2$,点亮$3$。

数据范围:

  对于$40\%$的数据,$1\leqslant n\leqslant 16$。
  对于$80\%$的数据,$1\leqslant n\leqslant 200$。
  对于$100\%$的数据,$1\leqslant n\leqslant 1,000,-100\leqslant a[u][v],b[u][v]\leqslant 100$,保证$p_u$是在$1$到$u-1$中等概率随机选取的。


题解

题目中不断强调数据随机,那么我们先挖掘一下其性质:

  $\alpha.$点$u$的期望深度为:$\mathbb{E}d_u=H_{u-1}$(其中调和级数$H=\sum \limits_{i=1}^n\frac{1}{i}=\ln n+\Theta(1)$);其实,你可以简单的将其理解为$u$的期望深度为$1+\ln_{u-1}$。

  $\beta.$点$u$的期望子树大小$\mathbb{E}z_u\leqslant \frac{n}{u}$。

  $\gamma.$树上每个节点的度数是$\log$级别的,这条性质对这道题没有帮助,自动忽略即可。

有了这些性质,考虑如何利用这些性质。

因为期望深度很小,也就是祖先很少,于是我们可以考虑用状压的方式记录其祖先的状态。

判断一棵子树内被点亮的点多还是没有被点亮的点多时需要将被点亮的点的状态存入其中进行转移,但是因为子树的期望大小,所以时间复杂度还是$\Theta(n^2)$级别的。

利用了这些性质,思考进一步解题。

既然已经说了用状压,那八成就是$DP$了,先作出如下定义$\downarrow$

  $\alpha.$设$dp[i][j][s]$表示以$i$为根的子树内有$j$个点被点亮,且从$i$到跟的路径上的点亮状态为$s$的最大贡献。

  $\beta.$设$f[0/1][i][s]$表示当$i$有没有被点亮的情况下从$i$到跟的路径上的点亮状态为$s$的最大贡献。

  $\gamma.$设$g[0/1][i][j]$表示当$i$有没有被点亮的情况下所有与$i$的$lca$为$j$的点的$a$或$b$数组的和。

$g$数组可以在$\Theta(n^2)$(均摊)的时间内暴力求出,注意这里没有必要使用倍增求$lca$。

利用$g$数组可以在$\Theta(n^2)$(均摊)的时间内求出$f$,然后做一个树上背包即可得到$dp$数组,初始$dp[0/1][i][0/1]=f[0/1][i][s]$。

$f$数组有空间问题,可以用$unordered\text{_}map$存储。

最后做三个证明。

$\mathcal{A}.$时间复杂度证明:

$$\begin{array}{ll}\Theta\left(\sum\limits_{u=1}^n2^{\mathbb{E}d_u}(\mathbb{E}z_u)^2\right) &=& \Theta\left(\sum\limits_{u=1}^n2^{\ln u}\left(\frac{n}{u}\right)^2\right) \\ &=& \Theta\left(\sum\limits_{u=1}^nu^{\ln 2}\left(\frac{n}{u}\right)^2\right) \\ &=& \Theta\left(\sum\limits_{u=1}^n\frac{n^2}{u^{2-\ln 2}}\right) \\ &\leqslant& \Theta\left(n^2\sum\limits_{u\geqslant 1}\frac{1}{u^{2-\ln 2}}\right) \\ &=& \Theta(n^2\zeta(2-\ln 2)) \\ &=& \Theta(n^2)\end{array}$$

但是实际上$\mathbb{E}(2^{d_u})\geqslant 2^{\mathbb{E}d_u}$,$\mathbb{E}(z_u^2)\geqslant (\mathbb{E}z_u)^2$,所以实际上时间复杂度大约是$\Theta(\frac{n^2\ln^2n}{\ln\ln n})$的。

$\mathcal{B}.$点$u$的期望深度$d_u$的证明:

边界$d_1=1$,那么:

$$d_u=1+\dfrac{\sum\limits_{v=1}^{u-1}d_v}{u-1}$$

再记:

$$D_u=\sum\limits_{v=1}^ud_v$$

带入上式,得:

$$D_u-D_{u-1}=1+\frac{D_{u-1}}{u-1}$$

整理,得:

$$\frac{D_u}{u}=\frac{1}{u}+\frac{D_{u-1}}{u-1}\Rightarrow\frac{D_u}{u}=H_u$$

所以:

$$d_u=1+\frac{D_{u-1}}{u-1}=1+H_{u-1}=\ln u+\Theta(1)$$

$\mathcal{C}.$点$u$的期望子树大小$z_u$的证明:

边界$z_n=1$,那么可以估算为:

$$z_u=1+\sum\limits_{v=u+1}^n\frac{z_v}{v-1}$$

再记:

$$Z_u=\sum\limits_{v=u}^n\frac{z_u}{v-1}$$

则:

$$z_u=(u-1)(Z_u-Z_{u+1})$$

带入原式,得:

$$(u-1)(Z_u-Z_{u+1})=1+Z_{u+1}$$

整理,得:

$$(u-1)Z_u=1+uZ_{u+1}\Rightarrow(u-1)Z_u=n-u+1$$

即:

$$Z_u=\frac{n-u+1}{u-1}$$

所以:

$$z_u=1+Z_{u+1}=\frac{n}{u}$$

时间复杂度:$\Theta(\frac{n^2\ln^2n}{\ln\ln n})$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec{int nxt,to;}e[1001];
int head[1001],cnt;
int n;
int fa[1001];
int a[1001][1001],b[1001][1001],size[1001],depth[1001];
int dp[2][1001][1001],g[2][1001][20],h[1001][1001];
bool now[1001];
unordered_map<int,int> f[2][1001];
int ans=-0x3f3f3f3f;
void add(int x,int y)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
void dfs(int x)
{
	size[x]=1;
	for(int i=head[x];i;i=e[i].nxt)
	{
		if(depth[e[i].to])continue;
		depth[e[i].to]=depth[x]+1;
		dfs(e[i].to);
		size[x]+=size[e[i].to];
	}
}
int LCA(int x,int y)
{
	int res=0;
	while(depth[y]<depth[x]){x=fa[x];res++;}
	while(depth[x]<depth[y])y=fa[y];
	while(x!=y){x=fa[x];y=fa[y];res++;}
	return res;
}
void dfs(int x,int s)
{
	now[x]^=1;
	memset(dp[now[x]][x],-0x3f,sizeof(dp[now[x]][x]));
	dp[now[x]][x][0]=f[0][x][s];
	dp[now[x]][x][1]=f[1][x][s];
	int sz=1;
	for(int i=head[x];i;i=e[i].nxt)
	{
		now[x]^=1;
		memset(dp[now[x]][x],-0x3f,sizeof(dp[now[x]][x]));
		memset(h[e[i].to],-0x3f,sizeof(h[e[i].to]));
		dfs(e[i].to,s<<1);
		dfs(e[i].to,s<<1|1);
		sz+=size[e[i].to];
		for(int j=0;j<=size[e[i].to];j++)
			for(int k=j;k<=sz;k++)
				dp[now[x]][x][k]=max(dp[now[x]][x][k],dp[!now[x]][x][k-j]+h[e[i].to][j]);
	}
	if(s&1)for(int i=((size[x]+1)>>1);i<=size[x];i++)h[x][i]=dp[now[x]][x][i];
	else for(int i=0;i<=((size[x]+1)>>1);i++)h[x][i]=dp[now[x]][x][i];
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++)
	{scanf("%d",&fa[i]);add(fa[i],i);}
	depth[1]=1;dfs(1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i==j)continue;
			scanf("%d%d",&a[i][j],&b[i][j]);
			int dep=LCA(i,j);
			g[0][i][dep]+=a[i][j];
			g[1][i][dep]+=b[i][j];
		}
	for(int i=1;i<=n;i++)
	{
		int state=1<<depth[i];
		for(int s=0;s<(1<<depth[i]);s++)
			for(int j=0;j<depth[i];j++)
				if((1<<j)&s)f[1][i][s]+=g[1][i][j];
				else f[0][i][s]+=g[0][i][j];
	}
	dfs(1,0);dfs(1,1);
	for(int i=0;i<=n;i++)ans=max(ans,h[1][i]);
	printf("%d",ans);
	return 0;
}

rp++

posted @ 2019-11-05 15:27  HEOI-动动  阅读(196)  评论(0编辑  收藏  举报