[XSY 3327][思维]跑步

题意
给你一个nn的网格。
初始时,每个网格有一个非负权值。有n次操作。
每次操作会将一个网格的权值加1或减1。(权值操作后仍非负)
输出当前每个网格到1
1的最长路(每步向上或向左)的总和。
n 2000 n\leqslant 2000
解法
这是一道好题!
我太菜了,做不出。
我们考虑维护dp值。
f i , j f_{i,j} 表示到(i,j)的最长路。
再令差分数组 g i , j = f i 1 , j f i , j 1 g_{i,j}=f_{i-1,j}-f_{i,j-1}
考虑一次加1或减1造成的影响。
显然,它会使每一行的一个区间的dp值造成加1或减1的影响。
设这些区间为 { l i , r i } \{l_i,r_i\} 那么我们有 l i l i + 1 , r i r i + 1 l_i\leqslant l_{i+1},r_i\leqslant r_{i+1}
利用差分数组我们可以在 O ( n ) O(n) 的时间内求出所有l和r并且修改对应的差分数组。
完结撒花!

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll Ans=0;
int n;
#define Maxn 2005
int a[Maxn][Maxn];
int dp[Maxn][Maxn],b[Maxn][Maxn];
int g[Maxn][Maxn];

inline void rd(int &x){
	x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
}

inline void Init(){
	for(register int i=1;i<=n;++i)
		for(register int j=1;j<=n;++j){
		    dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j];
		    g[i][j]=dp[i-1][j]-dp[i][j-1];	
		    Ans+=dp[i][j];
		}
}
inline void Modify(int x,int y,int sgn){
	int contain=0;
	int l=y,r=y;
	for(register int i=y+1;i<=n;++i){
		g[x][i]-=sgn;
		if(g[x][i]+sgn<0||(g[x][i]+sgn==0&&sgn==1))r=i;
		else break;
	}
	contain=r-l+1;
	for(register int i=x+1;i<=n;++i){
		while(l<=r){
			g[i][l]+=sgn;
			if(g[i][l]-sgn<0||(g[i][l]-sgn==0&&sgn==-1))l++;
			else break;
		}
		if(l>r)break;
		while(r<n){
			g[i][r+1]-=sgn;
			if(g[i][r+1]+sgn<0||(g[i][r+1]+sgn==0&&sgn==1))r++;
			else break;
		}
		contain+=r-l+1;
	}
	Ans+=sgn*contain;
}

char opt[5];

int main(){
	rd(n);
	for(register int i=1;i<=n;++i)
		for(register int j=1;j<=n;++j)rd(a[i][j]);
	Init();
    printf("%lld\n",Ans);
    int x,y;
    for(register int i=1;i<=n;++i){
    	scanf("%s",opt);
    	rd(x);rd(y);
    	if(opt[0]=='U')Modify(x,y,1);
    	else Modify(x,y,-1);
    	printf("%lld\n",Ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ezoilearner/article/details/85040655