1001-Theramore
来源: 杭电杯超级联赛8 算法: 贪心 题目链接: https://acm.dingbacode.com/contest/problem?cid=1051&pid=1001 补完: Yes 完成时间: August 11, 2022
题意
给定一个只含有‘0’和‘1’的字符串,有无数次机会可以选择长度为奇数的任意间隔并将其反转,输出能得到的字典序最小的字符串。
解法
因为只能选择长度为奇数的任意间隔,所以奇数位置上的字符只能移动到奇数位置,偶数位置上的字符只能移动到偶数位置上。例如:位置5上的’0‘是可以被移到位置1上的。
我们可以分别计算出奇数位置上和偶数位置上‘0’的个数,然后从前往后对应奇偶位置地放置这些’0‘(原来奇数位置上的’0‘还是要放在奇数位置上),放完’0‘后,就可以开始放’1‘,这样的字符串就是能得到的字典序最小的字符串。
完整代码
1 |
|
1004-Quel'Thalas
来源: 杭电杯超级联赛8 算法: 模拟 题目链接: https://acm.dingbacode.com/contest/problem?cid=1051&pid=1004 补完: Yes 完成时间: August 11, 2022
题解
问使用几条线可以覆盖\([0,0]\sim[n,n]\)且不包括\([0,0]\)的所有点, 以\(n\)条竖线和\(n\)条横线即可.
代码
1 |
|
1008-Orgrimmar
来源: 杭电杯超级联赛8 算法: 动态规划, 树形DP 题目链接: https://acm.dingbacode.com/contest/problem?cid=1051&pid=1008 补完: Yes 完成时间: August 11, 2022
题意简述
给一个有\(n\)个节点,\(n-1\)条边的无向连通图(树),任意删除一些点(以及连接它的边),使得剩下的点构成若干个离散集,其中离散集的定义是每个点至多连接了一条边。
求最多剩下多少点。
题目分析
——题意理解——
要让剩下的点满足这个离散集的定义,那么点要么干脆是孤立的点,要么是两个点由一条边相连。超过三个点相连就必定有一个点和另外两点相连,连接了至少两条边。
——初步思路——
首先我们考虑把树切割成链。
删除若干个点之后树变成了若干条链,那么对一条链,每三个点删除一个点,就能让所有的点都满足定义。
但是好难噢,树链剖分也不是删点剖的,连板子都没有。
构造一些样例尝试之后,也很容易发现删除点是有后效性的,贪心这条路可能也走不通。
具体表现在一个节点被删除之后,它的子树的状态会收到影响。
这么一看好像DP噢,试试,试试。
——经典引入——
本题的要求和一种经典的树形DP非常吻合——求树的最大独立集
独立集:图的一个顶点子集,该子集中的任意两个项点在图中不相邻。
首先,和大部分树形DP相同,整体上使用类似后序遍历的DFS跑,也就是先求子树的最优解,再合并求出亲节点的最优解,最后询问根节点的最优解。
再确定我们需要的变量。首先,必不可少的一个变量表示当前的节点编号;接着,在这个情景里,每个节点有两种选择:选入集合/不选入集合。
我们用\(f[i][0]\)表示不选i节点,用\(f[i][1]\)表示选\(i\)节点。DP函数\(f\)的值是以\(i\)为根节点的子树中的最大独立集节点数(如果点有权值,则是最大点权和)。
如果我们不选\(i\)节点,那么它所有的子节点都是可选择的。用\(j\)表示\(i\)的子节点,那么就有:
\(f[i][0]=\sum max(f[j][0],f[j][1])\) ;
如果我们选了\(i\)节点,那么它的所有子节点都不能选择,否则i就不是独立的点了。
\(f[i][1]=\sum f[j][0];\)
——迁移使用——
本题和求树的最大独立集的问题的差别在于,不要求选取的点集中所有的点都是独立的点,还可以有若干个两个节点相连的点对。
我们还是先试着用\(f[i][0]\)表示不选i节点,用\(f[i][1]\)表示选\(i\)节点。
如果我们不选\(i\)节点,那么它所有的子节点仍然都是可选择的。用\(j\)表示\(i\)的子节点,那么同样有:
\(f[i][0]=\sum max(f[j][0],f[j][1])\) ;
但是,如果我们选了i节点,并不是所有的子节点都不能选择。i可以不是独立的点,它可以和其中一个子节点连接成一个点对。
如果它和一个子节点连接成了一个点对,那么这个子节点的所有子节点,也就是i的孙辈节点都不能再选择了,否则就会形成一个三个点相连的链,不符合题目要求。
当然,也可以一个子节点都不选。这样i的孙辈节点仍然可以自由选择。
既然出现了分异,那么一个\(f[i][1]\)就不够用了。
我们使用\(f[i][1]\)表示和之前一样的,不选择任何子节点的情况。那么状态转移方程显然还是那个样子:
\(f[i][1]=\sum f[j][0];\)
另外加一个\(f[i][2]\)表示选了一个子节点的情况。
具体选哪个子节点,当然贪心地选择选了之后贡献最多的那个。记住选择这个点之后,孙辈的点不能选,所以选择的状态是\(f[j][1]\)而不可以是\(f[j][2]\)。
也就是\(max(f[j][1]-f[j][0])\)对应的点。
处理的时候让\(f[i][2]\)和\(f[i][1\)]先一起累计\(\sum f[j][0]\),最后\(f[i][2]\)加上选择的子节点多出来的贡献就行。
完整代码
1 |
|
1011-Stormwind
来源: 杭电杯超级联赛8 算法: 枚举, 贪心 题目链接: https://acm.dingbacode.com/contest/problem?cid=1051&pid=1011 补完: Yes 完成时间: August 11, 2022
题意简述
给一个边长\(n*m\)的矩形,\(n\)和\(m\)是整数。平行矩形边切割这个矩形,把它分成若干的小矩形。每次切割都贯穿矩形,也就是切割线一定要交于原矩形的两边。要求每个小矩形边长是整数且面积不小于k。
求最多切割次数。
题目分析
切割欸,乍一看好像不能贪心。
但由于本题切割要求的特殊性:每一次切割都贯穿矩形,也就是不会有呈T型或者L型镶嵌的矩形存在,小矩形一定是规规整整,每行每列固定数量整齐排列的。
其实也就是确定一个横着切几刀和纵着切几刀的方案。
那么我们是可以贪心的:显然每个矩形在满足面积大于k的情况下应该尽量小,这样才可能切出更多矩形,在这样规整的切割里也就是切更多次了。
也就是对于一个一边长为\(i\)的小矩形,我们找到最小的\(j\)作为另一个边长,满足\(i*j≥k\)。
那么\(j\)就是\(k/i\)向上取整。也就是确定\(i\)之后,\(j\)跟着确定了。
确定了小矩形的长和宽之后,我们可以直接计算出大矩形能切出几个小矩形来。
比如大矩形一边长为\(n\),对应小矩形的边长为\(i\),\(n/i\)向下取整就是分割数量,切割次数比这个值小1。
另一边同理。
这样以来,我们只要确定一个小矩形边长之一的\(i\),这个方案对应的答案就能计算出来了。
本题保证大矩形边长在\(1e5\)以内,显然遍历枚举\(1e5\)范围内的整数\(i\)是可以接受的。
甚至因为只需要满足\(i*j≥k\),所以i遍历到\(sqrt(k)\)就可以结束,再之后的\(ij\)数对是重复的(比如25和52)。
需要注意,如果计算出来一边切不出哪怕一个小矩形,也就是形如\(n<i\)的情况,那么这个答案是无效的。
最后使用一个变量更新最大答案即可。
完整代码
1 |
|