引言
acm中有些题目并不需要特定的算法去解决,而是靠一
些比较灵活的思维,这些题目第一次见往往很难被迅速
解决,因此本文目的是收集大量这种类型的题目,希望
从中发现它们的某些共性。
本文侧重于对于题目的思维分析,而且由于这些题目属于一旦想出解法就很容易码出代码的题目类型,出于节省版面与实践的缘故,不会给出代码。选题的范围广,包括但不限于区域赛、Wold Final、CF、Atcoder、各大OJ等等。
例题一
题目来源:2020上海高校程序设计竞赛暨第18届上海大学程序设计联赛夏季赛(同步赛)I题:露营
题面:
一句话题意:给定一个
n
∗
m
(
n
∗
m
≤
100000
)
\mathbf{n*m(n*m\le 100000)}
n∗m(n∗m≤100000)的矩阵以及
k
(
k
≤
20000
)
\mathbf{k(k\le 20000)}
k(k≤20000)个限制条件
(
x
i
,
y
i
,
h
i
)
(
h
i
≤
1000
)
\mathbf{(x_i,y_i,h_i)(h_i\le 1000)}
(xi,yi,hi)(hi≤1000),代表
(
x
i
,
y
i
)
\mathbf{(x_i,y_i)}
(xi,yi)处的高度被限制为
h
i
\mathbf{h_i}
hi,现让你为矩阵上每一个未被指定高度的点分配一个高度,使得矩阵上任意两个相邻点之间高度差等于1。
题解:考虑每次bfs从高度最低的点开始向四个方向扩展,每次扩展让新节点高度+1。最后再来检查全矩阵是否合法(相邻点是否满足高度差恰好为1)。
为什么这样做是正确的呢?首先,如果有两个点高度差小于距离差或者两个点之间的高度差与距离差奇偶性不同,意味着矩阵无法被合法的填充,这很容易证明(可以算一下样例加以领会)。当两点之间的高度差和距离差合法的时候,我们一定可以按照从低到高的方式填充。注意到每次填充都是取最低点,比如设矩阵第一行上的两个点(0,0),(0,5)高度已知为1和4,那么填充前它们之间的格子(包括自己)高度为1,x,x,x,x,4,填充以后为1,2,3,4,5,4,显然符合条件。
具体编程实现的时候,采用优先队列代替传统的队列来进行bfs操作即可。每次从优先队列中取出高度最小的点,并对周围的未指定高度的点进行更新,更新完所有点之后再对全矩阵进行检测看是否合法。
例题二
题目来源:International Collegiate Programming Contest (ACM-ICPC) World Finals 2015E题:Evolution in Parallel
题面:
CPU Time limit: 2 seconds
Memory limit: 1024 MB
一句话题意:给定一个字符串s以及
n
(
n
≤
4000
)
\mathbf{n(n\le 4000)}
n(n≤4000)个字符串,字符串各不相同且仅由A、C、M三种字符构成,所有字符串长度均不大于4000。问能否将n个字符串分为最多两个组,每个组满足字符串按长度从小到大排序后上一个字符串是下一个字符串的子序列。并且每个组中最长的字符串都是s的子序列。如果不能输出impossible,否则输出两个组中的字符串,如果方案存在多个可输出任意一个方案。(输入输出详见题面)
题解:本题的做法是贪心。考虑先建立两个组A、B,方便起见,两个组的第一个字符串都为s串,然后对n个串按长度从大到小逐个考虑。
下面定义四种当前字符串的类型。
1.当前字符串是A组中最后一个字符串的子序列但不是B组中最后一个字符串的子序列。
2.当前字符串是B组中最后一个字符串的子序列但不是A组中最后一个字符串的子序列。
3.既不满足类型1也不满足类型2的。
4.当前字符串既是是A组中最后一个字符串的子序列又是B组中最后一个字符串的子序列。
5.当前字符串是临时数组中的最后一个字符串的子序列。(要求临时数组不为空)
遍历的任何过程中如果出现了类型3的就输出impossible。刚开始在遍历的过程中如果出现了类型1或者类型2,就放入对应的组中即可。直到遇到一个类型4,就放入临时数组C中,后面新加入的元素都首先检查是否为类型5,如果是就放入组C中,否则就看能否加入A组中,能加入A组,C组中的元素就全部移入B组;反之,C组中的元素就全部移入A组。遍历完所有字符串后如果C组中还剩下元素,就全部加入任意一个组中。
分析一下这样贪心的正确性。首先最重要的一点:临时数组C中的元素,如果不是最后一个元素,那么对于后面新的元素的放置(即是放A组还是放B组)不会产生任何影响。
为什么这么说?假设有这么一段临时数组中的元素
c
1
,
c
2
,
c
3
.
.
.
c
k
\mathbf{c_1,c_2,c_3...c_k}
c1,c2,c3...ck以及第一个不能加入C组中的元素a,假设a能加入A组中。C组中的非末端元素(即
c
1
,
c
2
.
.
.
c
k
−
1
\mathbf{c_1,c_2...c_{k-1}}
c1,c2...ck−1)显然有可能也能加入A组,但是不管这些元素放哪个组,A组和B组的最后一个元素只能是a和
c
k
\mathbf{c_k}
ck,因为a和
c
k
\mathbf{c_k}
ck必须分开放,而a和
c
k
\mathbf{c_k}
ck的长度是不小于
c
i
(
1
≤
i
≤
k
−
1
)
\mathbf{c_i (1\le i\le k-1)}
ci(1≤i≤k−1)的。然后由于只有组末端元素影响后续元素的摆放,故临时数组非末端元素不影响后面新的元素的放置。
有了这一点之后,我们可以说每当遇到了第一个不能放进C组中的元素的时候,就必须决定该元素和C组中元素的去向,即是放A组还是B组,设C中最后一个元素是c,第一个不能加入C中的元素是a,那么因为新加入的元素长度必定大于这两个元素,只能放在这两个元素之后,而这两个元素又不能放同一个组,故必须决定该两个元素放哪个组。这样的话只需要看a元素放哪个组即可,C组中的元素都放相反的组即可(为了方便C中非末端元素都跟c放一个组即可)。
本题虽然是World Final的题,但是代码并不复杂,更重要的是理解这种贪心的思路。
参考代码点这里
例题三
待补充。。。