• hdu 4850 Wow! Such String! 构造 或 欧拉路径并改写成非递归版本


    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4850

    跟这道题也算是苦大仇深了...

    题意:构造一个由26个小写字母组成的、无长度为4的重复子串的字符串(要求出能构造出的最大长度)

    符合要求的长度为4的字符串有4^26个 容易猜最大长度是:4^26+3 = 456979

    比赛的时候想法是要让各个字母出现得尽量“均匀”

    用了一个cnt数组记录26个字母出现的次数 每次都选择出现次数最小的、不与前面重复的字母加上去

    然而稍微写得有点歪,最后构造出了长度为440000+的字符串,最后1万死活搞不出

    然后昨晚重新回来看这道题,按照比赛时的思路把写歪的地方扶正

    发现还是差几千

    然后就发现在“选择出现次数最小的、不与前面重复的字母”的地方,我每次都是一个循环从'a'找到‘z’

    这意味着如果出现多个符合要求的字母,字典序小的更有优势,这不太符合“均匀出现”的原则

    于是我加了个优化,从上一个字母的‘?'一直循环到‘(?+i)%26'来找,这样的话字典序小的字母就不具备什么竞争的优势了

    注意这种算法并不能把那种四个字母都一样的子串加进去,所以构造出来以后输出的地方要加特判

    然而从一开始这种构造方法就非常的魔性,你的起始字母选得不一样结果也不一样,非(sang)常(xin)神(bing)奇(kuang)!

    并且我一直在纠结起始那几个字符要不要计数,或者要计多少个...

    (因为这是一条链而不是一个环, 所以理论上貌似前三个字母一定会和最后三个字母相同)

    非(sang)常(xin)神(bing)奇(kuang)!

    一段乱试之后最长只能构造到476782+26... 心好累... 就差200不到了...

    努力了两三个小时没有成功,如果有大神用类似的做法做出来了麻烦告诉我

    我想,对于这种失败构造还是留个纪念吧

    代码如下:(并不能通过的代码)

    #include <cstring>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <iostream>
    #include <cstdio>
    #include <stack>
    #include <vector>
    #include <queue>
    #include <map>
    #include <set>
    
    using namespace std;
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> P;
    
    const int maxn = 500010;
    const int maxm = 30;
    const int INF = 0x3f3f3f3f;
    
    const int M1 = 10007;
    const ull B1 = 9973;
    int cnt[maxm];
    
    ull check[M1][100];
    int ccnt[M1];
    char ans[maxn];
    
    int solve()
    {
    
        ans[0] = 'a';
        ans[1] = 'a';
        ans[2] = 'a';
        ans[3] = 'b';
        cnt[0]++;
        cnt[0]++;
        cnt[0]++;
        cnt[1]++;
    
    /*
        ans[0] = 'z';
        ans[1] = 'z';
        ans[2] = 'z';
        ans[3] = 'a';
        cnt[25]++;
        cnt[25]++;
        cnt[25]++;
        cnt[0]++;*/
    
    
        ull e = 0;
        for(int i = 0; i < 4; i++)
            e = e * B1 + ans[i];
        check[e%M1][ccnt[e%M1]++] = e;
    
        ull t1 = 1;
        for(int i = 0; i < 4; i++)
            t1 *= B1;
    
        int loc = 4;
    
        int pre = 1;
        while(true)
        {
            e = e * B1 - t1 * ans[loc - 4];
    
            int mincnt = INF;
            int minsub = -1;
    
            for(int t = 0; t < 26; t++)
            {
                int i = (pre + t) % 26;
                ull tmpe = e + 'a' + i;
                int tmp = tmpe % M1;
                int s = ccnt[tmp];
                bool found2 = false;    //能不能放
    
                for(int j = 0; j < s && !found2; j++)
                {
                    if(check[tmp][j] == tmpe)
                        found2 = true;
                }
    
                if(found2)
                    continue;
                /*else                    
                {
                    minsub = i;
                    break;
                }*/
    
                if(cnt[i] < mincnt)     //选一个出现次数最少并且合法的字母
                {
                    mincnt = cnt[i];
                    minsub = i;
                }
            }
    
            if(minsub == -1)
                break;
    
            ans[loc++] = 'a' + minsub;
            e = e + 'a' + minsub;
            cnt[minsub]++;
    
            check[e%M1][ccnt[e%M1]++] = e;
            pre = minsub+1;
    
            /*if(loc %10000 == 0)
                cout << loc << endl;*/
        }
    
        return loc;
    }
    
    
    int main()
    {
        //freopen("in", "r", stdin);
        //freopen("out.txt", "w", stdout);
    
        int loc = solve();
    
        int n;
        int flag[maxm];
        while(scanf("%d", &n) == 1)
        {
            memset(flag, 0, sizeof(flag));
    
            if(n > (loc-1+26))
            {
                printf("Impossible
    ");
                continue;
            }
    
            int pre = -1;
            int precnt = 0;
            for(int i = 0; i < n; i++)
            {
                printf("%c", ans[i]);
    
                if(ans[i] == pre)
                    precnt++;
                else
                    precnt = 1;
    
                if(precnt == 3 && flag[ans[i] - 'a'] == 0 && i < n-1)
                {
                    printf("%c", ans[i]);
                    flag[ans[i] - 'a'] = 1;
                    n--;
                }
    
                pre = ans[i];
            }
    
            printf("
    ");
        }
    
        return 0;
    }

    续着上面的思路

    已经注意到如果从上一个字母的地方开始往下找会更均匀

    那么如果取消掉cnt[]数组可不可以呢,即:

    “对字母出现的次数不作严格的约束,让它自然地、均匀地跑”

    然后这种做法就真的是对的!(掀桌TAT

    代码如下:

    #include <cstring>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <iostream>
    #include <cstdio>
    #include <stack>
    #include <vector>
    #include <queue>
    #include <map>
    #include <set>
    
    using namespace std;
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> P;
    
    const int maxn = 500010;
    const int maxm = 30;
    const int INF = 0x3f3f3f3f;
    
    const int M1 = 10007;
    const ull B1 = 9973;
    int cnt[maxm];
    
    ull check[M1][100];
    int ccnt[M1];
    char ans[maxn];
    
    int solve()
    {
    
        ans[0] = 'a';
        ans[1] = 'a';
        ans[2] = 'a';
        ans[3] = 'b';
        cnt[0]++;
        cnt[0]++;
        cnt[0]++;
        cnt[1]++;
    
    /*
        ans[0] = 'z';
        ans[1] = 'z';
        ans[2] = 'z';
        ans[3] = 'a';
        cnt[25]++;
        cnt[25]++;
        cnt[25]++;
        cnt[0]++;*/
    
    
        ull e = 0;
        for(int i = 0; i < 4; i++)
            e = e * B1 + ans[i];
        check[e%M1][ccnt[e%M1]++] = e;
    
        ull t1 = 1;
        for(int i = 0; i < 4; i++)
            t1 *= B1;
    
        int loc = 4;
    
        int pre = 1;
        while(true)
        {
            e = e * B1 - t1 * ans[loc - 4];
    
            int mincnt = INF;
            int minsub = -1;
    
            for(int t = 0; t < 26; t++)
            {
                int i = (pre + t) % 26;
                ull tmpe = e + 'a' + i;
                int tmp = tmpe % M1;
                int s = ccnt[tmp];
                bool found2 = false;    //能不能放
    
                for(int j = 0; j < s && !found2; j++)
                {
                    if(check[tmp][j] == tmpe)
                        found2 = true;
                }
    
                if(found2)
                    continue;
                else                    //与上个代码不同的地方,找到了合法的字母马上退出
                {
                    minsub = i;
                    break;
                }
    /*
                if(cnt[i] < mincnt)
                {
                    mincnt = cnt[i];
                    minsub = i;
                }*/
            }
    
            if(minsub == -1)
                break;
    
            ans[loc++] = 'a' + minsub;
            e = e + 'a' + minsub;
            cnt[minsub]++;
    
            check[e%M1][ccnt[e%M1]++] = e;
            pre = minsub+1;
    
            /*if(loc %10000 == 0)
                cout << loc << endl;*/
        }
    
        return loc;
    }
    
    
    int main()
    {
        //freopen("in", "r", stdin);
        //freopen("out.txt", "w", stdout);
    
        int loc = solve();
    
        int n;
        int flag[maxm];
        while(scanf("%d", &n) == 1)
        {
            memset(flag, 0, sizeof(flag));
    
            if(n > (loc-1+26))
            {
                printf("Impossible
    ");
                continue;
            }
    
            int pre = -1;
            int precnt = 0;
            for(int i = 0; i < n; i++)
            {
                printf("%c", ans[i]);
    
                if(ans[i] == pre)
                    precnt++;
                else
                    precnt = 1;
    
                if(precnt == 3 && flag[ans[i] - 'a'] == 0 && i < n-1)
                {
                    printf("%c", ans[i]);
                    flag[ans[i] - 'a'] = 1;
                    n--;
                }
    
                pre = ans[i];
            }
    
            printf("
    ");
        }
    
        return 0;
    }

    最后,曹霸和彦志哥哥说他们当时是用欧拉路径做的

    我才恍然大悟,其实比赛的时候我也想到了这一点然而我只是用他去推理那个26^4+3的结论,却没有想到套用欧拉路径的算法

    长度为3的子串作为点,然后如果该子串的长度为2的后缀是另一个子串的前缀,则连边

    这样就有26^3个点,26^4条边

    然后自己写了一个发现本机上都跑不出来,原来是爆栈

    网搜了一下发现网上好多代码都是爆栈的好伐!写博客的人到底有没有好好把自己的程序跑一下!

    完全没有有用的信息= =

    然后今天早上补了一些知识

    1.递归压栈的时候出了记录变量以外还有很多奇怪的开销,而且效率也很低下(以前知道,然而这次更深刻的理解)

      所以改成非递归版本虽然也记录了变量,但是省下了大量的其他的开销

    2.个人感觉,虽然理论上不用goto也可以写出任何程序,然而有时候不用goto真的很不直观很难想啊

      这毕竟是一个竞赛,迫不得已的时候也不能执拗地拒绝goto

    然后就开始辛苦地DIY欧拉路径的非递归、临界表版本 Orz

    终于搞出来了激动得哭出来 T T

    不过还是学到了好多东西:

    1.goto语句

    2.手写栈!

    代码如下:

    #include <cstring>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <iostream>
    #include <cstdio>
    #include <stack>
    #include <vector>
    #include <queue>
    #include <map>
    #include <set>
    
    using namespace std;
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> P;
    
    const int maxe = 500010;
    const int maxv = 270000;
    const int INF = 0x3f3f3f3f;
    
    char rans[maxe];
    
    struct Edge
    {
        int to, next;
        bool vis;
    }edge[maxe];
    
    struct St
    {
        int u, i;
    }st[maxe];
    int head[maxv];
    int tot;
    
    int stcnt;
    
    void init()
    {
        tot = 0;
        memset(head, -1, sizeof(head));
    }
    
    void addedge(int u, int v)
    {
        edge[tot].to = v;
        edge[tot].next = head[u];
        edge[tot].vis = false;
        head[u] = tot++;
    }
    
    int euler(int U)
    {
        int loc = 0;
        int u = U;
        int i;
        while(true)
        {
            l1: i = head[u];
    
            for(; i != -1; i = edge[i].next)
            {
                if(edge[i].vis == true)
                    continue;
    
                edge[i].vis = true;
                st[stcnt].i = i;
                st[stcnt].u = u;
                stcnt++;
                u = edge[i].to;
                goto l1;
                //euler(edge[i].to);
    
                l2: rans[loc++] = edge[i].to % 26 + 'a';
                if(u == U && stcnt == 0)
                    goto l3;
            }
            --stcnt;
            i = st[stcnt].i;
            u = st[stcnt].u;
            goto l2;
        }
        l3: return loc;
    }
    
    int solve()
    {
        int num1 = 26;
        int num2 = num1*26;
        int num3 = num2*26;
        int num4 = num3*26;
    
        for(int i = 0; i < num3; i++)
            for(int j = 0; j < num1; j++)
                addedge(i, i%num2*num1+j);
    
        int loc =  euler(0);
        rans[loc++] = 'a';
        rans[loc++] = 'a';
        rans[loc++] = 'a';
    
        return loc;
    }
    
    int main()
    {
        //freopen("in", "r", stdin);
        init();
        int loc = solve();
        int n;
        while(scanf("%d", &n) == 1)
        {
            if(n > loc)
            {
                printf("Impossible
    ");
                continue;
            }
    
            for(int i = loc-1; i > loc-1-n; i--)
                printf("%c", rans[i]);
            printf("
    ");
        }
    
        return 0;
    }
  • 相关阅读:
    【转帖】android线程知识整理
    Andorid开发笔记
    Flex 4.6 手机项目第一次开发小记
    memory point
    两个sql server 2000的通用分页存储过程
    网页通用视频播放(asp.net2.0原作)
    c#操作XML常用方法
    也说项目开发经验
    SQL Server各种日期计算方法
    Server.Transfer()方法在页间传值
  • 原文地址:https://www.cnblogs.com/dishu/p/4520736.html
Copyright © 2020-2023  润新知