题解 P5019 【铺设道路】

引领天下

2018-11-13 22:06:34

Solution

这题目就我一个人模拟吗? 普及蒟蒻,不会好算法,就来说一发模拟吧 首先一个贪心: # 每次选一个连续正深度的坑的区间去填 为什么呢?因为只有这样,才能保证**我每次填坑的数量最多,不会造成浪费(即可以一天解决的问题我2天解决)**,也就是保证填的天数最少 于是得到了$ O(n^2* sum(a[1...n])) $,嗯,超时成什么样我就不说了 于是优化: 观察下面的表: | d[1] | d[2] | d[3] | d[4] | d[5] | d[6] | d[7] | | :----------: | :----------: | :----------: | :----------: | :----------: | :----------: | :----------: | | 1 | 4 | 3 | 3 | 4 | 3 | 3 | | 0 | 3 | 2 | 2 | 3 | 2 | 2 | | 0 | 2 | 1 | 1 | 2 | 1 | 1 | | 0 | 1 | 0 | 0 | 1 | 0 | 1 | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 可以发现,第2行与第3行实际上是重复的 在本例中,只重了2行; 如果重个10000,100000行呢? 那么你的程序就会T。 如何优化? 不难发现,我们填坑的过程是有规律的; 于是就有了优化: **每次循环必然要彻底填掉至少1个坑** 那么,实现就很简单了: 在找**连续整数的时候,顺便查找最小值**,然后区间减最小值,完成目标。 到此为止,你已经拿到80分了 还有两个点什么情况? 因为,尽管我们优化了,整个复杂度还是$ O(n^{2}) $,是会T的 再优化! 我们可以发现,**当我们找区间的开始的时候,其实是有单调性的** # 即:我下一次填坑的起始点一定在本次填坑的范围中 那么在模拟减的时候就可以顺便找下一次的开始了。 你也许发现了一个问题: > 一遍坑全部填平了怎么办? 没事,设定一个极值,最后判一下,如果没变的话就从本次的结束点往后找啦~ 于是复杂度降到了$ O(n+\text{常数}) $,就AC了 代码: ```cpp #include <bits/stdc++.h> using namespace std; int n,d[100005],q=1; long long s,ans; int main(){ scanf ("%d",&n); for (int i=1;i<=n;i++)scanf ("%d",&d[i]),s+=d[i];//读入,统计和 int b=1; while (d[b]==0)b++;//找开端 q=b;//下一个开端 while (s){ b=q; int e=b,mn=1<<20; while (d[e])mn=min(mn,d[e]),e++;//找最小值 ans+=mn,s-=mn*(e-b);//花mn天干,总和减去填的所有 q=1<<20; for (int i=e-1;i>=b;i--){d[i]-=mn;if (d[i]>0)q=i;}//模拟减,同时找下一次的开端 if (q==1<<20)for (q=e+1;q<=n;q++)if(d[q]>0)break; } printf ("%d",ans); } ```