• [USACO12NOV]同时平衡线Concurrently Balanced Strings DP map 思维


    题面

    [USACO12NOV]同时平衡线Concurrently Balanced Strings

    题解

    考虑DP。
    (f[i])表示以(i)为左端点的合法区间个数。令(pos[i])表示以(i)为左端点,最靠左的合法右端点。
    那么有如下转移:
    (f[i] = f[pos[i] + 1] + 1).
    1表示[i, pos[i]]这段合法区间,(f[pos[i] + 1])表示在这段合法区间的基础上,还可以在后面拼接更多合法区间。

    那么我们的目的就是求(pos[i]).
    考虑一段区间为什么合法?
    我们令左括号为1,右括号为-1,然后对于每一行单独求前缀和。
    那么对于每行单独考虑,一个区间([l, r])需要满足2个限制:

    • (sum[r] - sum[l - 1] = 0)
    • 区间内任意一点(i)满足(sum[i] - sum[l - 1] >= 0)

    考虑分开处理这2个限制。
    对于每个左端点(i),处理出使得区间前缀为负的第一个点,那么只要右端点不超过这个点,就满足第二点限制。
    对于同一列的每一行都求出这个值,然后取min,得到值(t),表示第(i)列的右端点只要不超过(t)就可以对于(k)行都满足第二点限制。
    令这个最小的,令左端点(i)不满足第二点限制的右端点为(lim[i])

    然后从大到小枚举列(i),那么我们要处理的就是第一点限制。
    只需要查询最近的一个右端点满足当前列的(k)行与第(i - 1)列的(k)行相同即可,
    如果这个最近的右端点都大于等于(lim[i]),那么对于这个左端点就没有合法方案了。
    否则我们就找到了(pos[i]).

    那么这两个东西应该如何求呢?

    (lim[i]):

    先对每一行求出对应的右端点,然后取(min)即可,接下来讲如何对于每行求这个右端点。
    如果有右端点满足使得以(i)为左端点的区间前缀和为负,那么肯定会有一个右端点最先满足这个东西,也就是(sum[j] - sum[i - 1] = -1).那么我们查询满足(sum[j] = sum[i - 1] - 1)的最小值即可。
    这个我们从大到小枚举(i),用(minn[i])表示到当前为止,值为(i)的点的最小下标是多少,然后就可以直接得到了。

    (pos[i]):

    用和上面类似的方法,只不过我们这次是要找一个使得(k)元组相等的最小(j)
    因为(k)很小,所以可以把(k)个值都存到(map)里暴力找。
    当然也有更优美一点的做法,对这(k)个数进行(hash)然后再丢(map)里面找,这样(map)就只用存1个数了。

    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define LL long long
    #define AC 12
    #define ac 100100
    #define inf 2139062143
    #define base 13
    #define us signed
    #define bu 16777215
    #define h(x) (x & bu)
    #define g(x) (x + 50000)
    
    int n, m; 
    LL ans, f[ac];
    int lim[ac], minn[ac];
    int Head[AC], date[ac], Next[ac], tot;
    char s[ac];
    map <us LL, int> M;
    struct node{
        int s[AC]; us LL opt;
        
        friend bool operator == (node a, node b)
        {
            for(R i = 1; i <= m; i ++) if(a.s[i] != b.s[i]) return false;
            return true;
        }
    }sum[ac];
    
    inline void upmax(int &a, int b) {if(b > a) a = b;}
    inline void upmin(int &a, int b) {if(b < a) a = b;}
    
    inline int read()
    {
        int x = 0;char c = getchar();
        while(c > '9' || c < '0') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x;
    }
    
    /*void ins(int w)//插入sum[w]
    {
        int f = h(sum[w].opt);bool done = false;
        for(R i = Head[f]; i; i = Next[i])
        {
            int now = date[i];
            if(sum[now] == sum[w]) upmin(date[i], w);//保留更小的下标
        }
        if(!done) Head[++ tot] = w, Next[tot] = Head[f], Head[f] = tot;
    }
    
    int find(int w)//找到值与sum[w]相同的,最小的x
    {
        int go = h(sum[w].opt);
        for(R i = Head[go]; i; i = Next[i])
        {
            int now = date[i];
            if(sum[now] == sum[w]) return now;
        }
        return inf;
    }*/
    
    void pre()
    {
        m = read(), n = read();
        for(R i = 1; i <= m; i ++)
        {
            scanf("%s", s + 1);
            for(R j = 1; j <= n; j ++) 
                sum[j].s[i] = sum[j - 1].s[i] + ((s[j] == ')') ? -1 : 1);
        }
    }
    
    void build()
    {
        for(R i = 1; i <= n; i ++) lim[i] = inf;
        for(R i = 1; i <= m; i ++)
        {
            memset(minn, 127, sizeof(minn));
            for(R j = n; j; j --)//对于每行的每个点找右边的,最近的,区间和为-1的点(比当前前缀小1
            {
                upmin(minn[g(sum[j].s[i])], j);//先加入,防止第一个括号是右括号
                upmin(lim[j], minn[g(sum[j - 1].s[i] - 1)]);//判断[l, r] = 0,需要用到sum[l - 1]和sum[r]
            }
        }
        for(R i = 1; i <= n; i ++)
            for(R j = 1; j <= m; j ++)
                sum[i].opt = sum[i].opt * base + sum[i].s[j];
    }
    
    void work()
    {
        for(R i = n; i; i --)
        {
            int pos = M[sum[i - 1].opt];//找最小的和当前l - 1前缀相同的前缀
            if(pos && pos < lim[i]) f[i] = f[pos + 1] + 1, ans += f[i];
            if(M[sum[i].opt]) upmin(M[sum[i].opt], i);
            else M[sum[i].opt] = i;
        }
        printf("%lld
    ", ans);
    }
    
    int main()
    {
    //	freopen("in.in", "r", stdin);
        pre();
        build();
        work();
    //	fclose(stdin);
        return 0;
    }
    
  • 相关阅读:
    【2020-11-01】从身边人开始输出自己的价值
    【一句日历】2020年11月
    【2020-10-31】继续解锁自己内心的矛盾
    【2020-10-29】静下心来,书中自有黄金
    【2020-10-28】平凡人终归还是要回归到小日子上
    【2020-10-27】抗衡自己的摇摆幅度
    【2020-10-26】市场驱动学习和进步
    【2020-10-25】窜着野炊的心干着农民的活
    暑假集训2016day3T1 欧拉回路(UOJ #117欧拉回路)(史上最全的欧拉回路纯无向图/有向图解析)
    leetcode1282
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10487114.html
Copyright © 2020-2023  润新知