算法竞赛经典例题
村庄债务问题
有一个村庄,村里有n户人家,他们整整齐齐地排成一列,编号从左向右依次是1~n
他们当中,有的欠别人钱,有的则被别人欠钱。今天,村委会决定统一处理这些债务。
然而这个村庄有一个神奇的特性,钱只能在相邻的人家之间流动,也就是说对于第i户,他的钱只能给第i-1户或第i+1户
如果1欠了3的钱,则必须先把钱给2,再由2给3
现在村委会想知道,他们至少需要多少次付钱的操作才能把所有债务问题解决。
输入
第一行,一个整数n
第二行n个整数,第i个数v[i]代表第i户人家的债务情况。正代表欠钱,负数代表被欠钱。
n<=1e5
v[i]<=1e9
输出 一个整数 表示最少的流动量
样例
输入
5
3 0 0 0 -3
输出
12
解释
1给2 3块钱 流动量+=3
2给3 3块钱 流动量+=3
3给4 3块钱 流动量+=3
4给5 3块钱 流动量+=3
流动量=12
这道题实际上是在说,经过若干次操作,一个东西由一个状态变成另外一个状态。而最终状态比较特殊,要求所有人既不欠钱,也不被欠钱。
也就是说所有v[i]都等于0
所有......v[i]......?有点麻烦,不如换个思路想想,v[i]=0对于任意i∈[1,n]均成立,那么对于i=1肯定成立
为什么要这么想呢,因为1的特殊位置,他只能和2发生关系,无论欠钱还是被欠钱,都只能通过2和别人交换。
但是对于2~n-1来说,他的左邻右舍都可以和他进行交换,问题会比较复杂。
所以我们先从最边上的1号入手
那么就不难想到一个算法
ans=0
for i=1 To n-1
ans+=abs(v[i]) //abs表示绝对值
v[i+1]+=v[i]
v[i]=0
print(ans)
翻转游戏(洛谷P1764)
就不写题面了,直接贴传送门(其实就是我懒得打了)
https://www.luogu.com.cn/problem/P1764
为了方便讨论,定义f(i,j)操作为:
翻转第i行第j列的棋子
翻转(i,j)上下左右的四个棋子
定义操作F(S)为:
对于所有(i,j)∈S
f(i,j)
可以想到一个O(2n*n)算法
枚举全体棋子的子集S对S中所有棋子进行f操作。操作完检查是否为同一种颜色
不过这个算法严重超时,一个世纪都跑不完(不排除一个世纪以内出现可以快速跑完这个算法的计算机)
不要被他的[提高+/省选-]的难度吓到,其实思路和刚才的题一样
所有的棋子都变成一种颜色,哪就先考虑全变成黑色,讨论出全是黑色的算法,那么白色同理
全是黑色,就意味着第一排的所有棋子都是黑色
为什么这么想呢?因为想要影响第一行,只有在第一行操作也就是f(1,1),f(1,2),f(1,3)...f(1,n)和在第二行操作,即f(2,1),f(2,2),f(2,3)...f(2,n)只有两行
然而对于第i行(i∈[2,n-1])能影响它的是第i-1行的操作,第i行的操作,第i+1行的操作,共三行
是不是像极了上一题?
这样就可以优化一下那个一个世纪都跑不完的算法了
原算法枚举了所有棋子的子集,我们可以枚举第一行棋子的子集S1
做一次F(S1)
等等,这样下来第一行很有可能是乱七八糟的啊
不要急,刚才说了,第一行可以被第一行和第二行的f操作影响,进行完F(S1)后,我们就不再直接操作第一行了
那么想把第一行全部变成黑色,只能通过操作第二行影响第一行
并且由于不再操作第一行了,那么想让第一行达到全黑,就可以确定唯一的第二行的子集S2使得F(S2)过后,第一行是全黑
F(S2)过后,第一行全黑,第二行很有可能是乱七八糟的,但这时候已经不能操作第一行和第二行了,只能通过操作第三行影响第二行
于是就可以确定唯一的第三行子集S3使得F(S3)过后,第二行是全黑
以此类推,可以由S1确定S2~Sn
在进行完Sn以后,1~n-1一定是全黑的,虽然第n行很可能是乱七八糟的,但 如果存在解,那么一定能枚举到一个S1使得第n行在操作完以后是全黑的
如果找不到,那么无解
伪代码如下
ans=+∞;
for S1⊆第一行:
F(S1)
temp=|S1| //|S1|表示S1的元素个数
for i=2 to n
for j= 1 to n
if (i-1,j)是白色:
f(i,j)
temp++
if 第n行全黑
ans=min(ans,temp)
if (ans<+∞) print(ans)
else print("Impossible")
顺序对计数
给定一个长度为n的序列a,求有多少(i,j)满足i<j且a[i]<a[j]
经典题目,用树状数组可以解决,这里就不详细说了(其实还是因为懒)
三元组计数
给定一个长度为n的序列a,求有多少(i,j,k)i<j<k且满足a[i]<a[j]<a[k]
n<=1e5
这是顺序对的加强版
这道题要从 j 上下手,数据规模提示我们时间复杂度应该是O(n)或O(nlogn)并且这是一个计数问题,算法的大体思路就是把a中所有的元素枚举一遍,对(i,j,k)进行统计
我们要枚举的正是j
为什么要枚举中间元素j呢?
如果我们能够知道对于每个j,有几个i满足i<j且a[i]<a[j],以及有几个k满足k>j且a[k]>a[j]
对于每个j,把满足条件的i的个数记作x[j],把满足条件的k的个数记作y[j]
那么答案就等于
Σx[i]*y[i]
接下来就是如何统计x数组和y数组
如果你会用树状数组求顺序对的话,这一步就很简单了
马拉松(题目来源:LUTECE)
由于LUTECE信号不好,就直接把题面贴过来了
一年一鸽的电子科技大学 IUPC 校队马拉松开始了!共有 n 名 IUPC 校队成员参加这场马拉松,所有人个人运动的速度是恒定的,并且可以被分解为 vx[i]和vy[i]
在0时刻,所有队员都出现在直线y=ax+b上
由于 IUPC 的优良传统,对于第 i 位成员,他每碰到一位其他成员(同一时刻出现在同一位置),他就会施展膜大佬技能,使自己的的膜法值加 1
现假设 IUPC 校队成员精力十足,从负无穷时刻开始跑,一直跑到正无穷时刻。zhw 大佬想知道所有参赛成员的膜法值之和是多少
Standard Input
第一行三个整数 n,a,b
接下来 n 行每行三个整数x[i] vx[i] vy[i]
Standard Output
一行一个整数代表答案。
说实话,拿到这道题的时候我懵逼了几分钟,随后我意识到了“所有人的起点都在一条直线上” 是一条多么重要的性质。
随后我想到了,相撞关系是不是具有传递性?也就是说a撞b,b撞c,那么a一定撞c?
答案是肯定的
你可以用代数方法证明,也可以用这种比较直观的证明方法证明

把一个人的速度v分解成沿直线方向 向右的速度va和垂直于直线方向 向上的速度vb (v,va,vb均为向量)
那么显然,|vb|相同且|va|不同的人一定会相撞(想想这是为什么)
直接把所有人以|vb|为第一顺序,以|va|为第二顺序排序
从左到右扫一遍,就能统计出答案
因为已经排好序了,|vb|相同的人已经排在了一起,分别扫描每一段|vb|相同的人,扫描的同时维护前面有几个人的|va|与当前正在扫描的人的不同(记为pre),每次ans+=pre*2即可
这道题的出题人可能不希望题目太毒瘤,把时间区间设为负无穷到正无穷,也就是说相撞时间可以为负的。如果时间只能为正,该怎么做呢?
把所有人以|vb|为第一顺序,以横坐标为第二顺序排序
如果两个人a,b的|vb|相同,a的横坐标<b的横坐标且a的|va|大于b的|va|那么他们会相撞
你想到了什么?
逆序对!
于是这道题的毒瘤加强版算法就这么出来了

浙公网安备 33010602011771号