【BZOJ3425】Poi2013 Polarization 猜结论+DP

【BZOJ3425】Poi2013 Polarization

Description

给定一棵树,可以对每条边定向成一个有向图,这张有向图的可达点对数为树上有路径从u到达v的点对(u,v)个数。求最小可达点对数和最大可达点对数

n<=250000

Sample Input

4
1 2
1 3
1 4

Sample Output

3 5

题解:想了一晚上,怎么想怎么是个搭建双塔,结果看题解发现还真tm是搭建双塔。

本题的结论有点神,不过很好猜,证明见Claris博客。

第一问的答案一定是n-1,因为树是个二分图,所以我们将树黑白染色后所有边都从黑点指向白点即可。

第二问的答案是什么呢?我们猜测一定是先选中一个点,然后一半的边都指向这个点,另一半的边从这个点指出去。而选择哪个点呢?显然是重心啦。然后我们将重心之外所有联通块的大小都拿出来,这就变成了搭建双塔问题。

话说搭建双塔不是$O(n^2)$的吗?n=250000你逗我?然而正解很奇特。

当联通块大小<sqrt(n)时,我们将所有这样的联通块合到一起跑分组背包;当联通块大小>sqrt(n)时,这样的联通块不超过sqrt(n)个,所以暴力DP即可。DP时用bitset维护,于是时间复杂度就变成了神奇的$O({n \sqrt{n}\over 32})$。

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <bitset>
#include <cmath>
using namespace std;
const int maxn=250010;
typedef long long ll;
int B,n,m,cnt,rt,mn;
ll sum,ans;
int to[maxn<<1],next[maxn<<1],head[maxn],siz[maxn],v[maxn],dep[maxn],s[maxn];
bitset<maxn> f;
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+(gc^'0'),gc=getchar();
	return ret*f;
}
inline void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
void dfs(int x)
{
	siz[x]=1,sum+=dep[x]-1;
	int i,tmp=0;
	for(i=head[x];i!=-1;i=next[i])	if(!dep[to[i]])
		dep[to[i]]=dep[x]+1,dfs(to[i]),siz[x]+=siz[to[i]],tmp=max(tmp,siz[to[i]]);
	tmp=max(tmp,n-siz[x]);
	if(tmp<mn)	mn=tmp,rt=x;
}
int main()
{
	n=rd(),B=int(sqrt(double(n))),mn=1<<30;
	memset(head,-1,sizeof(head));
	int i,j,a,b;
	for(i=1;i<n;i++)	a=rd(),b=rd(),add(a,b),add(b,a);
	dep[1]=1,dfs(1),memset(dep,0,sizeof(dep));
	sum=0,dep[rt]=1,dfs(rt);
	for(i=head[rt];i!=-1;i=next[i])	v[++m]=siz[to[i]];
	f[0]=1;
	for(i=1;i<=m;i++)
	{
		if(v[i]<=B)	s[v[i]]++;
		else	f=f|(f<<v[i]);
	}
	for(i=1;i<=B;i++)
	{
		for(j=1;j<=s[i];s[i]-=j,j<<=1)	f=f|(f<<(i*j));
		if(s[i])	f=f|(f<<(i*s[i]));
	}
	for(i=0;i<=n;i++)	if(f[i])	ans=max(ans,sum+(ll)i*(n-1-i));
	printf("%d %lld",n-1,ans);
	return 0;
}

 

转载于:https://www.cnblogs.com/CQzhangyu/p/7787776.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值