枚举算法经典例题
作者:互联网
村庄债务问题
有一个村庄,村里有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")
标签:第一行,S1,ans,算法,枚举,欠钱,第二行,操作,例题 来源: https://www.cnblogs.com/LMXZ/p/12072483.html