51 nod 1624 取余最长路 思路:前缀和 + STL(set)二分查找
?
寫這題花了我一上午時間。
?
下面是本人(zhangjiuding)的思考過程:
?
首先想到的是三行,每一行一定要走到。
大概是這樣一張圖
?
每一行長度最少為1。即第一行(i -1) >= 1,第二行 (j - i) >= 1,第三行 (n - j) >= 1。
?
我們要求的就是這條路徑上的和。
我們發現只要 i 和 j 固定了,結果就固定了。
如果每次都要重新求和的話時間復雜度肯定不夠,所以我這個時候想到了前綴和。
用sum[a][b]表示第a行(總共只有3行)前b個數字的和。
?
第一行可以用 sum[1][i]來表示,第二行可以用sum[2][j]-sum[2][i-1]來表示,第三行可以用sum[3][n]-sum[3][j-1]來表示。
(此處用到了i-1所以我們吧sum[][0]空出來,這樣就不會越界)
ans = sum[1][i] + sum[2][j] - sum[2][i-1] + sum[3][n] - sum[3][j-1];
每一組i、j都有一個固定結果,我們求出其中取模最大的那個就好了。
?
?
現在求和的問題解決了,該固定i , j 了。
下面看到i的取值范圍(1 <= i <= n),j的取值范圍(i <= j <= n)
如果遍歷i和j的話時間復雜度還是O(n^2),所以前綴和+暴力肯定是會超時的。
如果能優化一點到O(n*log(n))就好了,這樣就不會超時了。
那么能優化嗎?答案是肯定的。
?
我們先把我們的每一對i,j對應的ans分離一下。
定義up =?sum[1][i] - sum[2][i-1];
down =?sum[2][j] + sum[3][n] - sum[3][j-1];
這樣up中只含有i,down中只含有j。
因為我們知道結果只和 i , j 有關,
如果我們能把每一個up可能的down存起來,二分法找出那個能讓結果最大的down值,然后用up+down更新ans就好了。
這樣時間復雜度就只有O(n*log(n))了。
?
你可能看到這里還有疑問,先懷著疑問,后面我會把我碰到的問題和疑問都講出來。
?
先看代碼有助于理解:
#include <bits\stdc++.h> using namespace std; typedef long long ll;//定義結構體,value存輸入的值,sum存當前行的前綴和。 struct node{ll value;ll sum; }a[4][100001]; //ifstream in("in15.txt");int main(){int n;ll p;cin >> n >> p;for(int i = 1;i <= 3; i++){for(int j = 1;j <= n; j++){cin >> a[i][j].value;a[i][j].sum = a[i][j].value+a[i][j-1].sum; // 求前綴和。 }}ll ans = 0;set<ll> s; // 用set存當前i值可能碰到的j值對應的down。 set<ll>::iterator it;//反向遍歷是因為down的范圍可以慢慢擴張,不用刪除set中的元素 for(int i = n; i >= 1; i--){ll up = (a[1][i].sum-a[2][i-1].sum)%p; // 當前i值對應的up。 up = (up+p)%p; // 強行變成正數。 ll down = (a[2][i].sum+a[3][n].sum-a[3][i-1].sum)%p; // 當前i值對應的down。 down = (down+p)%p; // 強行變成正數。 // cout << "i:" << i << endl; // cout << "up:"<< up << endl; // cout << "down:" << down << endl; s.insert(down);//因為肯定是j >= i的,所以每次把當前i值對應的down加入到set,set就會表示可能取到的所有的j對應的down的集合。 //明確一點,set里裝的全是down。 // 二分查找,找到一個down值與up相加后最大。 it = s.lower_bound(p-up); // 找到第一個大于等于p-up的down值,我們需要將it--,求出第一個比p-up小的。//注意,1、2順序不能變。 if(it != s.begin()) { //1.如果等于it = s.begin(),因為it還要減一下,相當于沒有找到合適的值。 it--; // 2.it--,讓it指向第一個比p-up小的值。ans = max(ans,(up+*it)%p); // 更新 ans。 }//可能第一個比p-up小的down值加上up后還是比較小,反而down的最大值加上up后取模還比較大,所以話線性時間更新一下ans。 it = s.end();it--;ans = max(ans,(*it+up)%p); }cout << ans << endl;return 0; } //writed by zhangjiuding?
看到這里可能有一個最大的問題,那就是強行變正數的問題。
比如p = 10, up求出來的是-3,而down求出來是6,(-3 + 6)%10 = 3還是正數,強行將up變成正數是否會出錯呢?
答案是肯定不會, ? up 強行變正后是7,(7+6)% 10 = 3,還是3。
看起來邏輯性并不強,但是我試過了很多組數據都是一樣的結果,強行變正并不影響結果。
?
我的猜測是:因為數組中所有的數都是正數或者0,所以每一個up+down一定會大于0。
如果up小于0,那么它可以取到的所有的down都會使 up+down >= 0。
只是這些值是取過模的,所以會出現負數,我們需要將它放在0~(p-1)之間。
?
總結
以上是生活随笔為你收集整理的51 nod 1624 取余最长路 思路:前缀和 + STL(set)二分查找的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 51 nod 1097 拼成最小的数 思
- 下一篇: 51 nod 1495 中国好区间 奇葩