短路

来源:互联网 发布:手机透视物体软件 编辑:程序博客网 时间:2024/06/02 10:15

短路

第七套广播体操,原地踏步——走!

众所周知,跳蚤们最喜欢每天早起做早操,经常天还没亮就齐刷刷地站在操场做着反复纵跳热热身。跳晚国在研制三星 note7的时候注意到了这点,于是他们打算让炸弹更快地引爆,这样就可以消灭更多早起的跳蚤。

三星 note7的主板可以看作是由 (2n + 1) × (2n +1)个中继器构成的,某些中继器会有导线连在一起,左上角和右下角的中继器分别连着电源的正负极。

电流流过一根导线的时间可忽略不计,但当电流经过中继器时,会延缓一段时间再从中继器流出。这个时间只跟该中继器本身有关,我们把这段时间的长度称为中继器的延时值。

这些中继器由导线连接围成一个一个的层,同个层的中继器的种类都一样,而不同层的种类都不一样,可以发现总共有 n+1层。当 n = 4 时,主板大概长这样:


跳晚们打算再加几根导线将某些中继器连接起来.凭借发达的重工业,他们能生产出无数条导线。但由于主板的限制,他们的导线只能和主板四周的边平行,且其长度只够连接相邻两个中继器。

现在他们想知道,他们改造的三星 note7的电源正极流出的电流能在多短的时间到达电源负极从而造成短路,这样电池就会释放出巨大的能量摧毁跳蚤国的有生力量了。

请参考输入格式和样例配图来更好地理解题意。

输入格式

第一行一个正整数 n

第二行 n+1个正整数 a0,a1,…,an,表示从内到外每层的中继器的延时值,单位为秒。其中,第 i 行第 j 列(1≤i, j≤n)的中继器的延时值为amax( | i−n−1 |,| j−n−1 |)

对于所有数据,保证每个数都是不超过 109 的正整数。

输出格式

一个表示你的答案的整数。

输入样例1

1
1 2

输出样例1

9

样例解释

这个数据对应的主板如下所示:


显然,我们可以用导线改造成这样:


这样从左上角到右下角就会有条 {2,2,1,2,2}电流路径,耗时为 9 秒。

输入样例2

9
9 5 3 7 6 9 1 8 2 4

输出样例2

69

数据范围

测试点

n

1~3

≤5

4~5

≤1 000

6~7

≤5 000

8~10

≤105

当时的想法:

    画了几个样例,发现选择最短的路多走一些,答案会更优,而且发现要经过的点只有(2n+1)+(2n+1)个,我就想用指针来指每一层和每一列,通过比较左边与下面的数的大小,来决定要往下走还是往左走,后来发现了几个反例,这样选到最后不一定是最优的,说明这种粗暴的贪心是不可行。

部分分:

    50 分DP(注意数组要开到2*n+1)

    100分 递推+贪心

部分分做法:

    当时画图画了半天没有画出个所以然,而且一开始只给了第一个样例,也看不出什么东西,于是索性先打了一个暴力的DP。

    这种DP也是比较基础一点的。f[i][j]表示走到第(i,j)个点的最短时间。由f[i-1][j]和f[i][j-1]取min即可得出。与我一开始的贪心想法有异曲同工之妙,但是又能够保证正确性。

50分 DP代码如下:

#include#include#includeusing namespace std;const long long vmaxn=2005,oo=1000000000000000;long long n,a[vmaxn],f[vmaxn][vmaxn],ans;//注意数组的大小!!void step1(){f[1][1]=a[n]; for(int i=1;i<=2*n+1;i++){for(int j=1;j<=2*n+1;j++){long long v=max(abs(i-n-1),abs(j-n-1));if(i!=1) f[i][j]=f[i-1][j]+a[v];if(j!=1) f[i][j]=min(f[i][j],f[i][j-1]+a[v]);}//状态转移}cout<>n;for(int i=0;i<=n;i++) cin>>a[i];for(int i=1;i<=2*n+1;i++)for(int j=1;j<=2*n+1;j++)f[i][j]=oo;step1();return 0;}

我的研究:

    后来老师多放了第二个样例上去,然而比赛当时没怎么再在意这题,思索着第一题的骗分大法,回家以后研究了一下,有一定的思路。


    通过画一下第二个样例可以发现,69是由4+4+2+2+2+8+1+1……+1+8+2+2+2+4+4得来的,所有数值里面最小的1走了最多的次数。

    我在家里按照自己的理解又打了一次,信心满满地交上去,结果wrong了8个点,然后,就没有然后了。

 

正解:

    听了zyc同学的讲解,似乎没有受到什么特别大的启发。但晚上回去一想,却是发现正解的做法思路可能会更清晰一些,而且更简洁,没有我的算法那么迷。

    用f[i]记录到这个点左上角需要的最短时间,而用mi记录到这层之前的最小值,因为越往里面一层,走最小值下来会更优,而f[i]=f[i+1]+mi+a[i]即可(n从大到小枚举,题目条件)

举个例子:

       

    如样例中的左上角部分,由4+4+4+2走到左上角的8,明显没有4+4+2+2要优,如果再理解不了,模拟一下就知道这种做法的正确性了。

    当到2的那一层时,mi=4,f[9]正好为f[10](4)+mi(4)+a[i](2)=10,此时的mi为2;接着到f[8]=f[9]+mi+a[i]=20,此时的mi不变,接着是f[7]=f[8]+mi+a[i]=23,其实也是可以理解为在2那一层多走一个数,然后再往下来。由此可见,正确性是可以保证的。

    最后得出答案,枚举某一行走最长的路,即走一个倒的L字形。假设此时枚举到为i,而整一行的长度为2*N+1,画一下样例可以得到,单边不为a[i]的长度为n-i,则双边为2*(n-i),但因为f中已经把左上角的值计算了,所以要在此基础上减1,这样得出的即为这一行的时间,而这一列要走的时间与行相同,最后要加上的结果是2*(2*(n-i)-1)。但其实右上角的数是算重复了的,于是还要在最后减去一个。

    最后为2*(2*(n-i)-1)-1,再乘上这一段的时间a[i],再加上2*f[i](走到左上角的时间与走到右下角的时间一致,所以要乘二)。每一行都这样算一次,最终答案取min。

#include#includeusing namespace std;long long n,ans,a[200010],f[200010];int main(){freopen("2091.in","r",stdin);freopen("2091.out","w",stdout);cin>>n;if(n==0) {cout<>a[i];long long mi=a[n];//到当前的最小值f[n]=a[n];for(int i=n-1;i>=0;i--){f[i]=f[i+1]+mi+a[i];if(a[i]=0;i--){v=2*( f[i]+( 2*n+1-2*(n-i)-1 )*a[i] )-a[i];//2*n+1为整一行的个数,(n-1)为单边不为a[i]的数,因为有双边所以乘二//但是因为在f[i]中,左上角的值已经计算了,减一    //行列都加有重复,ans=min(v,ans);//取min}cout<



原创粉丝点击