码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-05-21 22:21:44      阅读:139      评论:0      收藏:0      [点我收藏+]

标签:

题目链接: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\n");
            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("\n");
    }

    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\n");
            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("\n");
    }

    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\n");
            continue;
        }

        for(int i = loc-1; i > loc-1-n; i--)
            printf("%c", rans[i]);
        printf("\n");
    }

    return 0;
}

 

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

标签:

原文地址:http://www.cnblogs.com/dishu/p/4520736.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!