Task
n*n的矩阵,初始位置在x,y,按照以下要求遍历:
下一步为x1,y1
① |x-x1|=1,|y-y1|>1或|y-y1|=1,|x-x1|>1
② Val[x1][y1]>val[x][y]
求最多遍历的位置个数。
50% n<=100
80% n<=1000
100% n<=1500,val<=1e6
Solution
观察条件②每个点向权值大的数转移。假如在序列中,相当于求单调递增子序列的最大长度(LIS)。题目是LIS的变形,转化成在矩阵中,有转移的限制条件①。
如果从点( i ,j )转移,如果不是从起点到( i , j )的最长长度,一定不是最优的,用这个长度更新其他点是没有意义的。因此对于每个点( I , j )我们需要的信息只有从源点到达(I,j)的最长长度(最优的信息),可以用dp处理。
50分暴力dp:
对于两个限制条件,先确定一个,再枚举另一个。
复杂度O(n^3)
当前点只可能从权值比它小的点转移过来,因此按照权值排序,处理当前i点,所有权值比它小的点都已经处理完毕,枚举符合条件的点,转移即可。
70分树状数组前后缀优化:
如果画出转移的图形,可以发现转移的是四周4行4列的前缀后缀信息。
树状数组可以胜任前缀后缀信息的维护。
100分正难则反 存最值
正难则反,不是考虑哪些点能转移,而是考虑哪些点不能转移,会发现,当前点对于每一行每一列不能转移的只有3个点。如果对于 每一行每一列都存最大的前4个,根据抽屉原理,必定可以找到一个转移,而且是最大的。
对于权值相同会互相影响结果的一段区间,为了避免这种影响,我们采用这一段区间都先查询完,再更新,更新时采用插入排序,找到第一个小的点,后面的点都往后移动一格,这样比每次swap更优。
这里的排序可以采用计数排序,O( n )!!
Cnt[i]表示权值<=i的个数,A[cnt[i]–]=i;
适用于权值种类不大(可以离散)的排序。
const int M=1505;
int rx[]={-2,-2,-1,1,2,2,1,-1},ry[]={1,-1,-2,-2,-1,1,2,2},rz[]={0,0,0,0,1,1,1,1};
int n;
/* 树状数组优化前缀 */
struct Tree{
int bit[2][M];
inline void update(int x,int c,int v){
if(c)x=n-x+1;//后缀
while(x<n){
MAX(bit[c][x],v);
x+=lowbit(x);
}
}
inline int query(int x,int c){
int mx=0;
if(c)x=n-x+1;
while(x>0){
MAX(mx,bit[c][x]);
x-=lowbit(x);
}
return mx;//!!
}
}Tx[M],Ty[M];//450w
struct node{
int x,y,v;
bool operator<(const node &t)const{
return v<t.v;
}
};
struct P70{
int A[M][M],dp[M*M],cnt[1000005];
int sx,sy,tot;
node C[M*M];//625w
inline void Sort(){//
int i,j,k;
rep(i,2,tot)cnt[i]+=cnt[i-1];//小于等于i的个数
rep(i,1,n)
rep(j,1,n)
C[cnt[A[i][j]]--]=(node){i,j,A[i][j]};
}
inline void input(){
int i,j,k;
rd(n);rd(sx);rd(sy);
rep(i,1,n){
rep(j,1,n){
rd(A[i][j]);
cnt[A[i][j]]++;
MAX(tot,A[i][j]);
}
}
Sort();
}
inline bool check(int x,int y){return x>=1&&x<=n&&y>=1&&y<=n;}
inline void solve(){
int ii,i,j,k,mx,x,y,nx,ny,ans=0,res,r;
input();
tot=n*n;
rep(i,1,tot)
if(C[i].x==sx&&C[i].y==sy){ii=i;break;}
for(k=ii;k<=tot;k=r+1){
r=k;
while(C[r].v==C[k].v)r++;r--;//权值相同一起处理
rep(i,k,r){
mx=0;x=C[i].x,y=C[i].y;
rep(j,0,7){
nx=x+rx[j],ny=y+ry[j];
if(!check(nx,ny))continue;
if(j==0||j==1||j==4||j==5)MAX(mx,Ty[ny].query(nx,rz[j]));
else MAX(mx,Tx[nx].query(ny,rz[j]));
}
dp[i]=0;
if(mx||(x==sx&&y==sy))dp[i]=mx+1;
MAX(ans,dp[i]);
}
rep(i,k,r){
x=C[i].x,y=C[i].y;
if(dp[i]){
Tx[x].update(y,0,dp[i]);
Tx[x].update(y,1,dp[i]);
Ty[y].update(x,0,dp[i]);
Ty[y].update(x,1,dp[i]);
}
}
}
printf("%d\n",ans);
}
}P70;
100分:
const int M=1503;
struct node1{
int x,y,v;
bool operator<(const node1 &t)const{
return v<t.v;
}
}C[M*M];
/* 每一行,列存前4个 */
struct node2{
int id,v;
inline void clear(){id=v=0;}
}T[2][M][5];
int dir[]={-1,1};
int A[M][M],dp[M*M],cnt[1000003];
int n,tot,sx,sy;
void Sort(){//计数排序
int i,j,k;
rep(i,1,tot)cnt[i]+=cnt[i-1];//<=i的个数
rep(i,1,n)
rep(j,1,n)C[cnt[A[i][j]]--]=(node1){i,j,A[i][j]};
}
void input(){
int i,j,k;
rd(n);rd(sx);rd(sy);
rep(i,1,n)
rep(j,1,n){
rd(A[i][j]);
cnt[A[i][j]]++;
MAX(tot,A[i][j]);
}
Sort();
}
inline int query(int c,int x,int y){
int i;
if(x<=0||x>n||y<=0||y>n)return 0;
rep(i,0,3)
if(abs(T[c][x][i].id-y)>1)return T[c][x][i].v;
return 0;
}
inline void update(int c,int x,int y,int v){
int i,pos=-1;
rep(i,0,3)
if(T[c][x][i].v<=v){pos=i;break;}
if(~pos){
per(i,3,pos+1)T[c][x][i]=T[c][x][i-1];
T[c][x][pos]=(node2){y,v};
}
}
void solve(){
int i,j,k,r,ii,mx,x,y,ans=0;
rep(i,0,1)
rep(j,0,n)
rep(k,0,4)T[i][j][k].clear();
rep(i,1,n*n)
if(C[i].x==sx&&C[i].y==sy){ii=i;break;}
dp[ii]=1;
for(k=ii;k<=n*n;k=r+1){
r=k;//val相同 同时查询 更新
while(C[r].v==C[k].v)++r;--r;
rep(i,k,r){
x=C[i].x,y=C[i].y,mx=0;
rep(j,0,1){
MAX(mx,query(0,x+dir[j],y));
MAX(mx,query(1,y+dir[j],x));
}
if(mx)dp[i]=mx+1;
}
rep(i,k,r){
if(dp[i]){
update(0,C[i].x,C[i].y,dp[i]);
update(1,C[i].y,C[i].x,dp[i]);
}
MAX(ans,dp[i]);
}
}
printf("%d\n",ans);
}
int main(){
input();
solve();
return 0;
}