BZOJ3939 BSOJ4853 【USACO 2015 FEB GOLD 】cow hopscotch

1 篇文章 0 订阅
1 篇文章 0 订阅
4853 -- 【USACO 2015 FEB GOLD 】cow hopscotch
Description
就像人类喜欢玩“跳房子”的游戏,农民约翰的奶牛已经发明了该游戏的一个变种自己玩。由于笨拙的动物体重近一吨打,牛跳房子几乎总是以灾难告终,但这是没有阻止奶牛几乎每天下午玩这个游戏。
游戏的矩阵共有R*C格(2 <= R <= 750,2 <= C <= 750),其中每一格是个正方形并被标记为一个整数K( 1 < = K <= R * C)。游戏是牛开始在左上角最后向右下角移动到的一个跳跃序列,牛从一个格子只能跳到在他的严格右下方(行列都要大于当前位置)与当前格子权值不同的格子上。请帮助奶牛计算不同可能的有效跳跃序列的数量。
Input
第一行三个整数 R, C, K.
以下R行,每行 C个整数.
Output
输出方案数,由于数太大,请对答案取模 1000000007.
Sample Input
4 4 4
1 1 1 1
1 3 2 1
1 2 4 1
1 1 1 1
Sample Output
5
【分析】动规,CDQ
题意:给你一个n(750)*m(750)的棋盘,每个格子(i,j)都有一种颜色c[i][j],颜色范围是[1,n*m]中的整数,对于一次跳跃A(y1,x1)->B(y2,x2),我们说这次跳跃是合法的,要求必须有:
1,y2>y1
2,x2>x1
3,c[y1][x1]!=c[y2][x2];
我们想要求出从(1,1)经过若干次跳跃之后到达(n,m)的方案数,
首先,我们可以构想一个暴力DP:
用f[i][j]表示从(1,1)到(i,j)的方案数
那么f[i][j]=∑f[u][v],u<i&&v<j&&c[u][v]!=c[i][j]
时间复杂度为O(n^2 m^2),想要通过这题,这还差得远。

思考1,
我们观察到,这道题中,颜色的范围不是1e9,而是5e5。为什么呢?
我们发现,相比较1e9,5e5这个范围的数组是开得下的,于是我们可以做计数处理,有助于我们维护答案。
(ps:如果颜色属于1e9范围,因为颜色种类最多只有n*m,所以其实我们可以离散化)
如果我们用d[x]表示颜色为从(1,1)走到当前处理范围颜色为x的点的方案数,
并用一个大的变量tot统计从(1,1)走到当前处理范围所有点的方案数之和。
那么当我们走到一个颜色为x的点时,就可以直接使用 tot-d[x] 来更新到达这个点的方案数。
这个思想有一定的价值,但是因为题目给出的1,2两个限制条件。
具体要通过什么途径保证d[]中处理的所有点都在它之间呢?

思考2,
区间问题的实现,很多时候我们都要引入整段考虑的思想。
这其实也就是CDQ分治的思想与具体实现。
我们想要更新从(1,1)走到行[l,r]范围格点的方案数。
而line[l,r]每个格子更新的来源,有两方面。
1,line[1,l-1]
2,line[l,r]内部
我们假设在处理[l,r]时,所有line[1,l-1]对line[l,r]的贡献已经生效。
于是现在只需要考虑来自于line[l,r]内部的贡献。
并且定义过程solve(l,r),用于解决[l,r]内部贡献的转移。
具体如何实现?
1,如果l==r,那不会生成任何贡献,直接结束。
2,否则我们设m=(l+r)>>1;
接下来只要依次实现以下3个步骤,这道题就做完了。
(1),solve(l,m);
(2),从[l,m]向[m+1,r]转移贡献;
(3),solve(m+1,r);
于是,关键落在要如何实现(2)上——
此时,我们发现行关系已经是严格的小于关系,只需要使得列关系满足要求。如何使得?
因为问题不是在数轴上,而是在矩形中。
所以目前处理的区间是一个块,行数是[l,r],每行有m列。
我们现在是想要把[l,mid]的状态传递到[mid+1,r]。
发现只需要从左向右一列列枚举。
状态结果的传递在(mid,r])实现,状态来源的累计在[l,mid]实现。
就保证了行与列都是严格小于关系,这道题也就在O(nmlogn)的时间复杂度内解决了。

设f[i][j]表示到(i,j)的方案数,则有

f[i][j]=∑f[x][y](x<i,y<j,a[x][y]!=a[i][j])

=∑f[x][y](x<i,y<j)−∑f[x][y](x<i,y<j,a[x][y]==a[i][j])

CDQ:按列分治处理
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 1010
#define MAXM 1010
#define ll long long
#define INF 1000000000
#define MOD 1000000007
#define eps 1e-8
ll f[MAXN][MAXN],s[MAXN*MAXN];
int  n,m,k, vis[MAXN*MAXN],T,a[MAXN][MAXN];
inline void ck(int x){
       if(vis[x]!=T)    s[x]=0;//本次第一次统计x颜色,原值清零
       vis[x]=T;//设x颜色标志
}
void CDQ(int l,int  r){
       if(l==r){
              return ;
       }
       Int i,j, mid=l+r>>1;
       CDQ(l,mid);//前半部分
    //前半部分对后半部分影响
       ll sum=0;
       T++;//本次是否已访问,T每次不同,这样vis不用每次清零,且用到时才清(快些!!)
       for(i=2;i<=n;i++){//针对每行
              for(j=l;j<=mid;j++){//前半部的列,累加方案数
                     (sum+=f[i-1][j])%=MOD;//总和,不管颜色
                     ck(a[i-1][j]);
                     (s[a[i-1][j]]+=f[i-1][j])%=MOD;//统计前半部分合法位置某颜色方案数的和
              }
              for(j=mid+1;j<=r;j++){//后半部的列
                     ck(a[i][j]);
                     (f[i][j]+=sum-s[a[i][j]]+MOD)%=MOD;//总的减去颜色相等的方案
              }
       }
       CDQmid+1,r);
}
int main(){
       int i,j;
       scanf("%d%d%d",&n,&m,&k);
       f[1][1]=1;
       for(i=1;i<=n;i++){
              for(j=1;j<=m;j++){
                     scanf("%d",&a[i][j]);
              }
       }
       CDQ(1,m);
       printf("%lld\n",f[n][m]);
       return 0;
}



法2:线段树

考虑暴力递推

f[i][j]=∑f[k][l],k<il<jcolor[i][j]!=color[k][l]

设F[i][j]表示走到(i,j)这个格子的方案数,那么转移方程就是严格左上的所有格子的F值和减去和他相同颜色的F值和,前者可以直接维护前缀和得出,后者可以对每种颜色建一个动态开点的线段树来维护,时间复杂度O(NMlognm),空间复杂度O(NMlognm)

代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 751
#define M 6000010
int mod=1e9+7;
using namespace std;
int f[N][N],a[N][N],pre[N][N];
int rt[N*N],ch[M][2],sum[M],cnt;
void addnew(int &y,int l,int r,int v,int w)
{
    if(!y) cnt++,y=cnt;
    sum[y]=(sum[y]+w)%mod;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(v<=mid) addnew(ch[y][0],l,mid,v,w);
    else addnew(ch[y][1],mid+1,r,v,w);
}
int check(int x,int l,int r,int ll,int rr)
{
    if(l==ll&&r==rr) return sum[x];
    int mid=(l+r)>>1;
    if(rr<=mid) return check(ch[x][0],l,mid,ll,rr);
    else if(ll>mid) return check(ch[x][1],mid+1,r,ll,rr);
else
 return (check(ch[x][0],l,mid,ll,mid)+check(ch[x][1],mid+1,r,mid+1,rr))%mod;
}
int main()
{
    int r,c,k;
    scanf("%d%d%d",&r,&c,&k);
    int i,j;
    for(i=1;i<=r;i++)
    {
        for(j=1;j<=c;j++)
        {
            scanf("%d",&a[i][j]);
            if(i!=1&&j!=1)
            f[i][j]=(pre[i-1][j-1]-check(rt[a[i][j]],1,c,1,j-1))%mod;
            else if(i==1&&j==1) f[i][j]=1;
        }
        for(j=1;j<=c;j++)
        {
            pre[i][j]=(((pre[i][j-1]+pre[i-1][j])%mod-pre[i-1][j-1])%mod+f[i][j])%mod;
            addnew(rt[a[i][j]],1,c,j,f[i][j]);
        }
    }
    printf("%d",(f[r][c]+mod)%mod);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值