HDU6766 Diamond Rush(可持久化线段树维护DP+线段树哈希)
HDU6766 Diamond Rush
题意:
给出一个矩阵,里面每个元素都带有一个权值。
单点i j的权值计算方式是(n*n)^(a(i,j))。
玩家从起点1 1开始,只能向右和向下移动。
每次询问会在矩阵中划分一个子矩阵区域,玩家不能走这个区域,询问玩家到达终点可以获得的最大权值。
题解:
一个很显然的思路是,建立两个二维DP数组,分别表示每个点距离起点的最大权值和距离终点的最大权值;再开一个二维数组记录每一行的前后缀DP最大值。那么对于每个询问,我们只需要枚举一组前后缀就可以得到答案。
但是如果这道题真的是这个我等凡人都可以想到的思路的话,那也太简单了。Claris大神在出这题的时候加了一个特殊的条件,就是每个点的点权计算公式。这会使得答案非常非常大,而在DP的过程中是不能取模的。这就使得这道题从一道签到题一跃变成了金牌题。
这里题解中给出的思路是,令cnt(i)表示经过一个方案里出现(n*n)^i元素的次数。考虑到指数的性质,我们比较两个方案的大小就可以转化为比较cnt(i)从大到小构成的字符串的字典序的大小!
那么如何比较两个方案的大小呢?Claris大神给出的一种方法是,给线段树增加一个哈希值,这样每个区间都有一个哈希值,对于两个方案递归比较即可。
观察DP过程可以发现,一个新的方案大部分继承上一个方案,cnt(i)单点更新,那么就可以想到用可持久化线段树维护这个过程。同时注意在计算方案的时候不要使用线段树合并,会造成时间复杂度的退化。
这里又有一个细节需要处理,就是一个点由两个方案相加而成,如何避免重复计算这个点的权值。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn=405;//最大的点数 const int M=1e7+100;//可持久化线段树的预期点数 const int mod=1e9+7;//模数 int t;//样例数 int n;//点数 int q;//询问数 int m;//线段树的预期大小,这里设为n*n int a[maxn][maxn];//原矩阵 int f[maxn][maxn];//正向DP数组,保存的是每个点相对于起点的最大方案 int g[maxn][maxn];//反向DP数组,保存方案的方式都是线段树的根节点 int w[maxn][maxn];//反向DP数组,但不包含当前节点的权值,这样就避免了重复计算 int pre[maxn][maxn];//每一行的前缀最大值 int suf[maxn][maxn];//每一行的后缀最大值 //前后缀最大值的保存方式是下标 int tot;//动态开点 int lson[M];//左儿子 int rson[M];//右儿子 int c[M];//每种数的出现次数 ll ans[M];//答案数组 ull Hash[M];//哈希数组 ull P[M];//预处理好的哈希数组 ll weight[M];//预处理好的节点权值数组 int up (int x,int l,int r,int p,int v) { int y=++tot; c[y]=c[x]+1; ans[y]=(ans[x]+weight[p]*v)%mod; Hash[y]=Hash[x]+P[p]*v; if (l==r) return y; int mid=(l+r)>>1; if (p<=mid) lson[y]=up(lson[x],l,mid,p,v),rson[y]=rson[x]; else lson[y]=lson[x],rson[y]=up(rson[x],mid+1,r,p,v); return y; } int query (int A,int B,int C,int D) { //ABCD表示四颗线段树的根节点 //如果A+B>C+D 返回1 //这样就省去了合并的时间 if (Hash[A]+Hash[B]==Hash[C]+Hash[D]) return 0; int l=1,r=m; while (l<r) { int mid=(l+r)>>1; if (Hash[rson[A]]+Hash[rson[B]]==Hash[rson[C]]+Hash[rson[D]]) { r=mid; A=lson[A]; B=lson[B]; C=lson[C]; D=lson[D]; } else { l=mid+1; A=rson[A]; B=rson[B]; C=rson[C]; D=rson[D]; } } return c[A]+c[B]>c[C]+c[D]; } int main () { scanf("%d",&t); P[0]=1; for (int i=1;i<M;i++) { P[i]=P[i-1]*13331; } while (t--) { scanf("%d%d",&n,&q); weight[0]=1; tot=0; m=n*n; for (int i=1;i<=m;i++) weight[i]=weight[i-1]*n%mod*n%mod; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%d",&a[i][j]); for (int i=0;i<=n+1;i++) for (int j=0;j<=n+1;j++) f[i][j]=g[i][j]=pre[i][j]=suf[i][j]=w[i][j]=0; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { int tt=query(f[i-1][j],0,f[i][j-1],0)?f[i-1][j]:f[i][j-1]; f[i][j]=up(tt,1,m,a[i][j],1); } for (int i=n;i;i--) for (int j=n;j;j--) { w[i][j]=query(g[i+1][j],0,g[i][j+1],0)?g[i+1][j]:g[i][j+1]; g[i][j]=up(w[i][j],1,m,a[i][j],1); } for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { pre[i][j]=query(f[i][pre[i][j-1]],w[i][pre[i][j-1]],f[i][j],w[i][j])==1?pre[i][j-1]:j; } for (int i=n;i;i--) for (int j=n;j;j--) suf[i][j]=query(f[i][suf[i][j+1]],w[i][suf[i][j+1]],f[i][j],w[i][j])==1?suf[i][j+1]:j; while (q--) { int xl,xr,yl,yr; scanf("%d%d%d%d",&xl,&xr,&yl,&yr); //比较xl-1行的yr+1后缀和xr+1行的yl-1前缀 int tt=query(f[xl-1][suf[xl-1][yr+1]],w[xl-1][suf[xl-1][yr+1]],f[xr+1][pre[xr+1][yl-1]],w[xr+1][pre[xr+1][yl-1]])?1:2; if (tt==1) { printf("%lld\n",(ans[f[xl-1][suf[xl-1][yr+1]]]+ans[w[xl-1][suf[xl-1][yr+1]]])%mod); } else { printf("%lld\n",(ans[f[xr+1][pre[xr+1][yl-1]]]+ans[w[xr+1][pre[xr+1][yl-1]]])%mod); } } for (int i=0;i<=tot;i++) { lson[i]=rson[i]=c[i]=Hash[i]=ans[i]=0; } } }