51nod 1624 取餘最長路 (set+二分查詢) 真.好題
阿新 • • 發佈:2019-02-12
思路:
核心思路是寫出結果的表示式,發現只有兩個變數,所以可以列舉一個變數二分查詢另一個變數。由於依靠結果的表示式,我感覺這個題的思路不好想。
首先說,因為取模後會有後效性,或者說區域性最優不能保證整體最優,所以不能採用 DP 的做法。由於只有 3 行,所以我們只要確定兩個拐點的位置就能確定答案,然鵝,正常思路下不列舉所有的路線是無法知道誰是最優的……
我們設 sum[ i ][ j ] 為第 i 行前 j 個元素之和,設兩個拐點的橫座標為 x 和 y。則結果為:
sum[ 1 ][ x ] + ( sum[ 2 ][ y ] - sum[ 2 ][ x-1 ]
) + ( sum[ 3 ][ n ] - sum[ 3 ][ y-1 ] )
由於要列舉一個變數二分查詢另一個變數,我們把含有 x 的表示式取出來:
sum[ 1 ][ x ] - sum[ 2 ][ x-1 ] ,剩下的表示式為:
sum[ 2 ][ y ]
+ ( sum[ 3 ][ n ] - sum[ 3 ][ y-1 ] )
. 由於兩部分的和應該 < p,我們可以列舉含有 y 的表示式,然後二分查詢 ( p - 含有 y 的表示式 ),如果能使結果更大則更新。
為了便於排序和二分查詢,我們可以把只含 x 的部分:sum[ 1 ][ x ] - sum[ 2 ][ x-1 ] 放入一個集合中。
( sum[ 3 ][ n ] - sum[ 3 ][ y-1 ] )
這部分也可以用第三行的字尾和表示,會加快速度。
注意:
如果 lower_bound() 查詢到的是第一個位置,則說明集合中所有數都大於等於查詢的數,滿足條件的只可能是第一個位置上的數。如果不是第一個位置,則位置的前一位是最優的。
程式碼:
#include<stdio.h> #include<iostream> #include<set> using namespace std; typedef long long LL; int a[4][100010],sum[4][100010]; int main() { int i,j,n,p; int val,ans; set<int> st; set<int>::iterator it; while(~scanf("%d%d",&n,&p)) { sum[1][0]=sum[2][0]=0; for(i=1;i<=3;i++) for(j=1;j<=n;j++) { //輸入矩陣並求前兩行的字首和 scanf("%d",&a[i][j]); if(i!=3) sum[i][j]=(sum[i][j-1]+a[i][j])%p; } sum[3][n+1]=0; //求第三行的字尾和 for(j=n;j>=1;j--) sum[3][j]=(sum[3][j+1]+a[3][j])%p; st.clear(); //清空集合 ans=0; for(i=1;i<=n;i++) { //遍歷每一個 y,二分求最優的 x val=(sum[1][i]-sum[2][i-1])%p; val=(val+p)%p; //可能為負數 st.insert(val); //把只含 x 的部分插入到集合 val=(sum[3][i]+sum[2][i])%p; //只含 y 部分的值 it=st.lower_bound(p-val); //二分查詢 if(it==st.begin()) { //如果插入位置是第一位 if((*it+val)%p>ans) ans=(*it+val)%p; } else { it--; if((*it+val)%p>ans) ans=(*it+val)%p; } } printf("%d\n",ans); } return 0; }