传送门:51nod 1624
思路:
核心思路是写出结果的表达式,发现只有两个变量,所以可以枚举一个变量二分查找另一个变量。由于依靠结果的表达式,我感觉这个题的思路不好想。
首先说,因为取模后会有后效性,或者说局部最优不能保证整体最优,所以不能采用 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;
}