BZOJ3939 BSOJ4853 【USACO 2015 FEB GOLD 】cow hopscotch

来源:互联网 发布:oppoa100软件下载 编辑:程序博客网 时间:2024/05/18 14:24
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-8ll 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 6000010int 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);}


0 0
原创粉丝点击