1. 程式人生 > >51nod 1624 取餘最長路 (set+二分查詢) 真.好題

51nod 1624 取餘最長路 (set+二分查詢) 真.好題

思路

核心思路是寫出結果的表示式,發現只有兩個變數,所以可以列舉一個變數二分查詢另一個變數。由於依靠結果的表示式,我感覺這個題的思路不好想。

首先說,因為取模後會有後效性,或者說區域性最優不能保證整體最優,所以不能採用 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;
}