zoukankan      html  css  js  c++  java
  • 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
    ",(ans[f[xl-1][suf[xl-1][yr+1]]]+ans[w[xl-1][suf[xl-1][yr+1]]])%mod);
                }
                else {
                    printf("%lld
    ",(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;
            }        
        }
    }
  • 相关阅读:
    LoadRunner利用ODBC编写MySql脚本(转)
    未在本地计算机上注册 microsoft.jet.oledb.4.0 提供程序
    趣文:舌尖上的程序猿
    Hadoop之我见
    C语言的经典排序算法源码
    Oracle自学笔记(一)
    log4j.properties 详解与配置步骤总结
    修改oracle用户密码永不过期
    Android 发送短信总结
    CEF禁止右键菜单
  • 原文地址:https://www.cnblogs.com/zhanglichen/p/13709774.html
Copyright © 2011-2022 走看看