[bzoj3939_Usaco2015 Feb]Cow Hopscotch(线段树维护DP)

              \;\;\;\;\;\;\;今天晚上,想到自己的线段树专题中的题还未码完。              \;\;\;\;\;\;\;此时,我看了看手边安详的作业,痛下决心,晚上熬夜补吧(唉~ 竞赛生的艰难啊 ~)。              \;\;\;\;\;\;\;于是,便有了我今日的博客。洛谷题面 [bzoj3939_Usaco2015 Feb]Cow Hopscotch以上链接便是相应的题目;拿到这道题,我们可以分析一下题目性质。对于点(x,y)点(x,y)点
摘要由CSDN通过智能技术生成

今天晚上,想到自己的线段树专题中的题还未码完。
此时,我看了看手边安详的作业,痛下决心,晚上熬夜补吧(唉~ 竞赛生的艰难啊 ~)。
于是,便有了我今日的博客。
洛谷题面 bzoj3939 Cow Hopscotch

以上链接便是相应的题目;

拿到这道题,我们可以分析一下题目性质。

对于 点 ( x , y ) 点(x,y) (x,y)来说,能跳到它上面的点的集合为:
∑ i = 1 x − 1 ∑ j = 1 y − 1 ( i , j ) ( a [ i ] [ j ] ! = a [ x ] [ y ] , a 数 组 代 表 编 号 ) \sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}(i,j)(a[i][j]!=a[x][y],a数组代表编号) i=1x1j=1y1i,ja[i][j]!=a[x][y]a

遇到这种在二维数组内求解方案数的题目,我们会很自然而然地想到区间DP,更何况n,m的数值还比较小。最暴力的方法,便是四重循环,在外面两层枚举行与列,在内两层枚举1~ x-1, 1 ~ y-1。然后,将符合条件的f[i][j]赋值到f[x][y]上。

但是, n 4 n^4 n4的时间复杂度,肯定会爆。(但据说数据太水了, n 4 n^4 n4的算法都可以过。)

那么,我们就需要线段树这种数据结构来维护了。

根据二维前缀和思想,我们可以再开一个数组sum,记录f(也就是DP数组)的前缀和。
那么,对于当前的 f [ x ] [ y ] f[x][y] f[x][y],我们可以用 s u m [ x − 1 ] [ y − 1 ] sum[x-1][y-1] sum[x1][y1]减去 ∑ i = 1 x − 1 ∑ j = 1 y − 1 f ( i , j ) \sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}f(i,j) i=1x1j=1y1fi,j,且 a [ i ] [ j ] = a [ x ] [ y ] a[i][j]=a[x][y] a[i][j]=a[x][y]

前缀和的问题我们不用考虑了,剩下的就是怎么快速求:
∑ i = 1 x − 1 ∑ j = 1 y − 1 f ( i , j ) ( a [ i ] [ j ] = a [ x ] [ y ] ) \sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}f(i,j)(a[i][j]=a[x][y]) i=1x1j=1y1fi,ja[i][j]=a[x][y]

思考一下,每个点的编号最大是 n ∗ m n*m nm,且n,m都是比较小的常数。那么,我们可以对每个编号开一个线段树,用来记录从上到下,编号为i的点的f值。

那么,对于当前的点 ( x , y ) (x,y) (x,y), ∑ i = 1 x − 1 ∑ j = 1 y − 1 f ( i , j ) ( a [ i ] [ j ] = a [ x ] [ y ] ) \sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}f(i,j)(a[i][j]=a[x][y]) i=1x1j=1y1fi,ja[i][j]=a[x][y]就可以在a[x][y]所代表的线段树中查找区间 ( 1 , y − 1 ) (1,y-1) 1y1的值(因为我们是按行开始从上往下进行处理的,所以x行以下的数就不会存进去)。

思路便是如此。

时间复杂度为: O ( n ∗ m ∗ l o g n m ) O(n*m*log nm) O(nmlognm)

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mo=1e9+7;
const int M=1e6+10;
struct ao
{
	int ls,rs;
	int num;
	#define ls(x) tree[x].ls
	#define rs(x) tree[x].rs
	#define num(x) tree[x].num
}tree[M*15];
int n,m,k,a[800][800],cnt,root[600000];
ll f[800][800],sum[800][800];
void change(int &p,int l,int r,int wei,int val)
{
	if(!p) p=++cnt;
	num(p)+=val;
	num(p)%=mo;
	if(l==r) return;
	int mid=l+r>>1;
	if(wei<=mid) change(ls(p),l,mid,wei,val);
	else change(rs(p),mid+1,r,wei,val);
}
ll cz(int p,int l,int r,int L,int R)
{
	if(!p) return 0;
	if(l>=L&&r<=R)
	{
		return num(p)%mo;
	}
	ll ans=0;
	int mid=l+r>>1;
	if(L<=mid) ans+=cz(ls(p),l,mid,L,R)%mo;
	if(R>mid) ans+=cz(rs(p),mid+1,r,L,R)%mo;
	return ans%mo;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
	}
	f[1][1]=sum[1][1]=1;
	change(root[a[1][1]],1,m,1,f[1][1]);
	for(int i=2;i<=n;i++)
	sum[i][1]=1;
	for(int i=2;i<=m;i++)
	sum[1][i]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=2;j<=m;j++) f[i][j]=(sum[i-1][j-1]%mo-cz(root[a[i][j]],1,m,1,j-1)%mo+mo)%mo;;
		for(int j=2;j<=m;j++) sum[i][j]=(sum[i-1][j]%mo+sum[i][j-1]%mo+f[i][j]-sum[i-1][j-1]%mo+mo)%mo;
		for(int j=2;j<=m;j++) change(root[a[i][j]],1,m,j,f[i][j]);
	}
	cout<<f[n][m];
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值