题目链接: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; }