题目

题目大意

给你一棵树,在树上的某一些节点上面有人,要用最小的步数和,使得这些人靠在一起。所谓靠在一起,即是任意两个人之间的路径上没有空的节点(也就是连在一起)。

N≤200N \leq 200N≤200


思考历程

看了题目好久,没有什么思路。

想到DP,但不知道怎么用DP做。

然后去翻翻题解,然后一脸懵逼,再去问问几位大佬。

LYL、ZHJ、GMH这三个大爷都说这题很水,是联赛难度。

不屑于给我讲。

天哪,这就是人与人之间的差距!太恐怖了。

然后我只能依靠我自己硬是刚了四天,对,是四天。

终于搞了出来……


正解

首先这题是一个树形DP。

看到这题的数据范围这么小,嗯,先估计一个时间复杂度。

就是O(N3)O(N^3)O(N3)了

就这么随意

然后想想DP的状态

设fi,jf_{i,j}fi,j​表示在iii这个节点的子树中(除iii以外),留下了jjj个人的最优解。(**在这个时候,其它的人被丢到了iii处

所谓的jjj个东西要和iii接在一起。

注意,对于iii这棵子树,有可能会有其它的节点进去里面,所以,这个jjj可以大于sumsonsum_{son}sumson​(sumisum_isumi​表示iii这个子树中的人的数量)

然后考虑如何转移。

我们分类讨论:

  1. 如果没有人在sonsonson这个节点上,那么sonsonson这棵子树里面的人一定都会跑出来(不然就断了),所以留下的为空。那么此时fx,i=fson,0+sumson+fx,if_{x,i}=f_{son,0}+sum_{son}+f_{x,i}fx,i​=fson,0​+sumson​+fx,i​
  2. 如果有人在sonsonson这个节点上,设留在sonsonson这棵子树内的人数为jjj。显然j≠0j \neq 0j̸​=0

    注意一下我设的这个状态,你会发现这个状态不包括这个根节点。

    如果有人在sonsonson这个节点上,那么子树剩余的人数为j−1j-1j−1。

    由于有sumson−jsum_{son}-jsumson​−j个人要出来,所以加上它们的贡献。注意,有可能是进去的,所以要用绝对值。

    fx,i=fson,j−1+∣sumson−j∣+fx,i−jf_{x,i}=f_{son,j-1}+|sum_{son}-j|+f_{x,i-j}fx,i​=fson,j−1​+∣sumson​−j∣+fx,i−j​

DP搞完了,然后呢?每次换根重新操作?

当然不存在的,那样就有可能T飞了。

我们枚举每一个节点,将其作为最后形成的块的顶端。

在它子树内的东西都已经搞好了,所以,只需要搞一搞子树外的东西。

然后我们就可以发现只需要将它子树外的人到它距离之和加起来就好了。

因为这个点是整块的顶端,所以那些点必定会钻到下面去。

钻到下面之前肯定要先来到这个地方,剩下的就加上DP出来的结果就行了。

那么用什么来处理这个距离的和呢?

一开始,由于强迫症,我想用一些好的方法。

后来想想,反正范围这么小,那么简单粗暴一下就好了。

直接枚举子树外面的节点,然后一个一个地将距离加上(Floyed预处理)。

判断是否在子树内用dfndfndfn序

时间复杂度O(N3)O(N^3)O(N3)

是不是和预想的一模一样


其实这个题真的有点奇怪,的确不是很难,但是容易误入歧途。

在一开始,我设的状态为:fi,jf_{i,j}fi,j​表示在iii的子树内,留下jjj个。

和最后的状态有什么区别呢?区别其实不大,就是包不包括iii这个节点。

然后我发现这样设繁琐至极,自己在推理的过程中绕来绕去的,很晕。

因为在这个东西中,我需要考虑一下根节点有没有人。

如果一开始没有人,那就要拿下面的补上,然后我又想着要开多一维来记录目前的这一块中的深度最小的点,用它来补上去,然后特别麻烦,怎么转移都不知道。

后来想想,我的DP可以设再设一下,表示根节点是否有东西……

我们知道在最后根节点一定有东西,或者所有的东西都跑到外面去,不可能出现根节点为空,而它的儿子的子树中还有东西。

那么在转移时还是有点麻烦。

最后,经过GMH的一点指导,然后我就又想了好久,才想了出来。

状态的这一点改动,实际上是解法的一个大大的更新。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define N 200
#define INF 400000
int n;
char a[N+3];
struct EDGE{
int to;
EDGE *las;
} e[N*2+1];
int ne;
EDGE *last[N+1];
inline void link(int u,int v){
++ne;
e[ne].to=v,e[ne].las=last[u];
last[u]=e+ne;
}
int dfn[N+1],nowdfn;
int siz[N+1],sum[N+1];
int f[N+1][N+1];
void dp(int,int);
int dis[N+1][N+1];
void dfs(int,int);
int ans;
int main(){
freopen("party.in","r",stdin);
freopen("party.out","w",stdout);
scanf("%d%s",&n,a+1);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
link(u,v),link(v,u);
}
for (int i=1;i<=n;++i)
fill(f[i],f[i]+n+1,INF);
dp(1,0);
for (int i=1;i<=n;++i)
fill(dis[i],dis[i]+n+1,INF);
for (int i=1;i<=n;++i)
dis[i][i]=0;
for (int i=1;i<=n;++i)
for (EDGE *ei=last[i];ei;ei=ei->las)
dis[i][ei->to]=dis[ei->to][i]=1;
for (int k=1;k<=n;++k)
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
ans=INF;
dfs(1,0);
printf("%d\n",ans);
return 0;
}
void dp(int x,int fa){
dfn[x]=++nowdfn;
siz[x]=1;
if (a[x]=='0')
sum[x]=0;
else
sum[x]=1;
f[x][0]=0;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa){
dp(ei->to,x);
siz[x]+=siz[ei->to],sum[x]+=sum[ei->to];
for (int i=siz[x]-1;i>=0;--i){//i倒着枚举,就像是背包问题一样。另外,由于目前的节点总数为siz[x],而x不算,所以最多为siz[x]-1
//转移,具体理由见上
f[x][i]=f[ei->to][0]+sum[ei->to]+f[x][i];
for (int j=1;j<=i;++j)
f[x][i]=min(f[x][i],f[ei->to][j-1]+abs(sum[ei->to]-j)+f[x][i-j]);
}
}
}
void dfs(int x,int fa){
int s=0;
for (int i=1;i<=n;++i)
if (a[i]=='1' && !(dfn[x]<=dfn[i] && dfn[i]<dfn[x]+siz[x]))//后面的这个东西判断它是否在子树里面
s+=dis[i][x];
ans=min(ans,s+f[x][sum[1]-1]);//因为总共的人数是sum[1],而有一个在x上,所以为sum[1]-1
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa)
dfs(ei->to,x);
}

总结

首先,在膜拜一下三个大佬。

还有后来随随便便AC的ZSH、PZM大佬。

对于在一棵树上又看起来不好用数据结构什么的东西来做的题目的时候,就想想能不能树形DP。

对于这题,树形DP的最大的妙处在于根节点不包括在内。

根节点有时候会处于一种比较尴尬的状况,所以,我们就要对它特殊地对待。

然后在状态中就把根节点排除在外,暂时不考虑。

其它的事

我翻了翻几位大佬的代码,都是枚举根节点然后搞一遍DP。

你们怎么不会TLE?而且,为什么,为什么时间还比我短!!!

有种被欺骗了的感觉。

还有这题的数据是真的水。

我的代码之前有一个很严重的错误,然而,带着这个错误,我居然有90分!

So strange!

这个错误是,Floyed中初值没有赋值好……

所以我不保证我现在这个代码一定是对的,但这个思想还是值得借鉴。

JZOJ[5971]【北大2019冬令营模拟12.1】 party(1s,256MB)的更多相关文章

  1. jzoj5990. 【北大2019冬令营模拟2019.1.6】Bear (状压dp)

    题面 题解 我永远讨厌dp.jpg 搞了一个下午优化复杂度最后发现只要有一个小trick就可以A了→_→.全场都插头dp就我一个状压跑得贼慢-- 不难发现我们可以状压,对于每一行,用状态\(S\)表示 ...

  2. jzoj5991. 【北大2019冬令营模拟2019.1.6】Juice

    题面 题解 好迷-- //minamoto #include<bits/stdc++.h> #define R register #define ll long long #define ...

  3. jzoj5989. 【北大2019冬令营模拟2019.1.6】Forest (set)

    题面 题解 为了一点小细节卡了一个下午--我都怕我瞎用set把电脑搞炸-- 观察一次\(1\)操作会造成什么影响,比如说把\(A[i]\)从\(x\)改成\(y\): \(D[x]\)会\(-1\), ...

  4. jzoj5984. 【北大2019冬令营模拟2019.1.1】仙人掌 (分块)

    题面 题解 数据结构做傻了.jpg 考虑每一个节点,它的儿子的取值最多只有\(O(\sqrt {m})\)种,那么可以用一个双向链表维护儿子的所有取值以及该取值的个数,那么对儿子节点修改一个值就是\( ...

  5. jzoj5983. 【北大2019冬令营模拟2019.1.1】多边形 (组合数学)

    这其实是道打表题--你看我代码就知道了-- 咳咳来点严谨证明好了-- 前方高能请注意 首先,正多边形近似于圆,可以看做在圆里内接多边形.圆内接多边形最多只有三个锐角.因为凸多边形的外角和为\(360\ ...

  6. [JZOJ5977] 【清华2019冬令营模拟12.15】堆

    题目 其中n,q≤500000n,q\leq 500000n,q≤500000 题目大意 让你维护一个堆.支持一下操作: 在某个点的下面加上另一个点,然后进行上浮操作. 询问某一点的权值. 思考历程 ...

  7. [2018冬令营模拟测试赛(二十一)]Problem A: Decalcomania

    [2018冬令营模拟测试赛(二十一)]Problem A: Decalcomania 试题描述 输入 见"试题描述" 输出 见"试题描述" 输入示例 见&quo ...

  8. noip模拟12[简单的区间·简单的玄学·简单的填数]

    noip模拟12 solutions 这次考试靠的还是比较好的,但是还是有不好的地方, 为啥嘞??因为我觉得我排列组合好像白学了诶,文化课都忘记了 正难则反!!!!!!!! 害没关系啦,一共拿到了\( ...

  9. 2019中山纪念中学夏令营-Day9[JZOJ](第六次模拟赛)

    Begin (题目的排序方式:Unkown其实是按心情排的) 异或:(摘自百度百科) 异或(xor)是一个数学运算符.它应用于逻辑运算.异或的数学符号为“⊕”,计算机符号为“xor”.其运算法则为: ...

随机推荐

  1. Gamma(1)

    目前为止看到的解释Gamma来由说得最清楚的一篇文章:https://www.cambridgeincolour.com/tutorials/gamma-correction.htm 几点总结. 1, ...

  2. kubernetes忘记token或者token过期怎么加入k8s集群

    1.先查看token是否还可用 [root@hadoop01 ~]# kubeadm token list 1.1) 还在则获取ca证书sha256编码hash值,不在则进行2操作 openssl x ...

  3. (转)C# 使用UDP组播实现局域网桌面共享

    转:http://www.cnblogs.com/mobwiz/p/3715743.html 最近需要在产品中加入桌面共享的功能,暂时不用实现远程控制:参考了园子里的一些文章,加入了一些自己的修改. ...

  4. (2)python tkinter-按钮.标签.文本框、输入框

    按钮 无功能按钮 Button的text属性显示按钮上的文本 tkinter.Button(form, text='hello button').pack() 无论怎么变幻窗体大小,永远都在窗体的最上 ...

  5. CSS 继承和优先级

    CSS继承性 CSS属性继承:外层元素的样式,会被内层元素进行继承. 多个外层元素的样式,最终都会“叠加”到内层元素上. 什么样的CSS属性能被继承呢? CSS文本属性都会被继承的: color. f ...

  6. day 51 阿里iconfont的使用

    阿里iconfont的使用   1. 找到阿里巴巴图标库 2.找到图标 3.搜索你想要的图标 4.将图标添加到购物车 5.点击右上角的购物车按钮,我这里添加了两个. 6.提示你登陆,不需要花钱,找其中 ...

  7. map 与 lambda 的用法

    # 列表中的每个元素进行*2lis = [10, 30, 70]f = map(lambda li: li*2, lis) # 操作lis中的每个元素.print(list(f)) # 返回来一个新的 ...

  8. WPF drag过程中显示ToolTip.

    原文:WPF drag过程中显示ToolTip. 在drag/drop过程中,我们在判断出over的元素上是否可以接受drag的东西之后,通常是通过鼠标的样式简单告诉用户这个元素不接受现在drag的内 ...

  9. mysql初次使用

  10. 初探Remax微信小程序

    1.创建项目 npx degit remaxjs/template-wechat my-app cd my-app && npm install 2.运行项目 npm run dev ...