【刷题】BZOJ 3522 [Poi2014]Hotel

时间:2023-04-20 20:20:26

Description

有一个树形结构的宾馆,n个房间,n-1条无向边,每条边的长度相同,任意两个房间可以相互到达。吉丽要给他的三个妹子各开(一个)房(间)。三个妹子住的房间要互不相同(否则要打起来了),为了让吉丽满意,你需要让三个房间两两距离相同。

有多少种方案能让吉丽满意?

Input

第一行一个数n。

接下来n-1行,每行两个数x,y,表示x和y之间有一条边相连。

Output

让吉丽满意的方案数。

Sample Input

7

1 2

5 7

2 5

2 3

5 6

4 5

Sample Output

5

HINT

【样例解释】

{1,3,5},{2,4,6},{2,4,7},{2,6,7},{4,6,7}

【数据范围】

n≤5000

Solution

先写了个普通的方法,

就是枚举每一个点,计算这个点为选的三个点的lca的方案数

这个只要在枚举了lca后遍历它的每个子树,当前子树内的一个点可以贡献之前的子树中深度与它相同的点中选两个的方案数,处理一下就好了

以下代码是可以过的:

#include<bits/stdc++.h>
#define ui unsigned int
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
const int MAXN=5000+10;
int n,e,beg[MAXN],nex[MAXN<<1],to[MAXN<<1],sum[MAXN];
ll ans,val[2][MAXN];
template<typename T> inline void read(T &x)
{
T data=0,w=1;
char ch=0;
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
x=data*w;
}
template<typename T> inline void write(T x,char ch='\0')
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
if(ch!='\0')putchar(ch);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
inline void insert(int x,int y)
{
to[++e]=y;
nex[e]=beg[x];
beg[x]=e;
}
inline void dfs(int x,int f,int dep)
{
ans+=val[1][dep],sum[dep]++;
for(register int i=beg[x];i;i=nex[i])
if(to[i]==f)continue;
else dfs(to[i],x,dep+1);
}
int main()
{
read(n);
for(register int i=1;i<n;++i)
{
int u,v;read(u);read(v);
insert(u,v);insert(v,u);
}
for(register int i=1;i<=n;++i)
{
for(register int j=beg[i];j;j=nex[j])
{
dfs(to[j],i,1);
for(register int j=1;j<=n;++j)val[1][j]+=val[0][j]*sum[j],val[0][j]+=sum[j],sum[j]=0;
}
for(register int j=1;j<=n;++j)val[0][j]=val[1][j]=0;
}
write(ans,'\n');
return 0;
}

之后为了做升级版,写了个没用长链剖分的 \(O(n^2)\) dp,也是当做一个过渡吧

设 \(f[u][k]\) 表示 \(u\) 的子树中距离 \(u\) 为 \(k\) 的点的个数, \(g[u][k]\) 表示 \(u\) 的子树中到LCA距离为 \(d\) ,\(u\) 到LCA距离为 \(d−k\) 的点对的数量。

转移就见程序吧,因为转移的顺序是对转移有影响的

这个程序被卡空间了,过不去,但是正确性是能够保证的:

#include<bits/stdc++.h>
#define ui unsigned int
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
const int MAXN=5000+10;
int n,e,beg[MAXN],nex[MAXN<<1],to[MAXN<<1];
ll f[MAXN][MAXN],g[MAXN][MAXN],ans;
template<typename T> inline void read(T &x)
{
T data=0,w=1;
char ch=0;
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
x=data*w;
}
template<typename T> inline void write(T x,char ch='\0')
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
if(ch!='\0')putchar(ch);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
inline void insert(int x,int y)
{
to[++e]=y;
nex[e]=beg[x];
beg[x]=e;
}
inline void dfs(int x,int p)
{
f[x][0]=1;
for(register int i=beg[x];i;i=nex[i])
if(to[i]==p)continue;
else
{
dfs(to[i],x);
for(register int j=0;j<=n;++j)
{
ans+=f[x][j]*g[to[i]][j+1]+(j?f[to[i]][j-1]*g[x][j]:0);
g[x][j]+=g[to[i]][j+1]+(j?f[x][j]*f[to[i]][j-1]:0);
if(j)f[x][j]+=f[to[i]][j-1];
}
}
}
int main()
{
read(n);
for(register int i=1;i<n;++i)
{
int u,v;read(u);read(v);
insert(u,v);insert(v,u);
}
dfs(1,0);
write(ans,'\n');
return 0;
}