P3120 [USACO15FEB]Cow Hopscotch G 题解

题目链接

P3120 [USACO15FEB]Cow Hopscotch G

solve

读完题目我们很容易写出一个 (O(n^4)) 的方程

[F[i][j]=sum F[i'][j'] ]

这个柿子可以通过三维偏序来优化,但是我往另外一个方向走了

如果颜色数很少的话,就可以用一个数组 (sum[i][j][k]) 表示第 (k) 个颜色在 (i,j) 左上角的方案书之和,(sum[i][j][0])表示总的,所以方程就变成了

[F[i][j]=sum[i-1][j-1][0]-sum[i-1][j-1][a[i][j]] ]

但是这里的颜色数达到了 (n imes m) 的级别,但要改的点数不多,就可以考虑用线段树的动态开点来维护前缀和

我们每行每行处理,先求后改,开 (k) (颜色数) 颗线段树,于是方案就变成了

[sum[j-1]- ext{第a[i][j]的1到{j-1}的前缀和} ]

每次求完回去再同意修改即可

代码实现

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=755;
const LL TT=1e9+7;
int N,M,cnt;
int a[maxn][maxn];
LL F[maxn][maxn],S[maxn];
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
struct Tree{
	LL sum;
	int ls,rs;
}tr[maxn*maxn*16];
inline void update(int x){
	tr[x].sum=(tr[tr[x].ls].sum+tr[tr[x].rs].sum)%TT;
}
void modify(int &x,int l,int r,int pos,int data){
	if(!x)x=++cnt;
	if(!(l^r)){tr[x].sum=(tr[x].sum+data)%TT;return ;}
	int mid=(r-l>>1)+l;
	(pos<=mid)&&(modify(tr[x].ls,l,mid,pos,data),0);(pos>mid)&&(modify(tr[x].rs,mid+1,r,pos,data),0);
	update(x);
	return ;
}
LL query(int x,int l,int r,int L,int R){
	if(!x)return 0;
	if(L<=l&&r<=R)return tr[x].sum;
	int mid=(r-l>>1)+l;
	LL ret=0;
	(L<=mid)&&(ret=(ret+query(tr[x].ls,l,mid,L,R))%TT,0);(R>mid)&&(ret=(ret+query(tr[x].rs,mid+1,r,L,R))%TT,0);
	return ret;
}
int main(){
	freopen("P3120.in","r",stdin);
	freopen("P3120.out","w",stdout);
	N=read();M=read();cnt=read();
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			a[i][j]=read();
	for(int i=1;i<=M;i++) S[i]=1;F[1][1]=1;modify(a[1][1],1,M,1,1);
	for(int i=2;i<=N;i++){
		for(int j=2;j<=M;j++)F[i][j]=(S[j-1]-query(a[i][j],1,M,1,j-1)+TT)%TT;
		LL res=0;
		for(int j=2;j<=M;j++){
			res=(res+F[i][j]);
			S[j]=(S[j]+res)%TT;
			modify(a[i][j],1,M,j,F[i][j]);
		}
	}
	printf("%lld
",F[N][M]);
	return 0;
}
原文地址:https://www.cnblogs.com/martian148/p/15474861.html