CF tracker
Edu List
https://codeforces.com/contest/1739
F. Keyboard Design
n个字符串 si, 价值ci
包含的字符都是 ‘a’~’l’ (前12个), 保证每个字符串中相邻字符不同
求 价值最大的 字符串价值集合
满足:
可以制作一个'a'~'l'
出现且只出现一次的字符串t, 使得集合中
所有字符串si中相邻的字符,在t上也相邻
范围
n 1000
sum |si| 2000
ci [1,1e5]
4s
1024mb
我的思路
2000 这数字有点假, 因为既然是前12个, 真的有用的配对是 11+10+9+..+1 = 12 * 11/2 = 66个
但是 66 个去做bitmask就不现实了,
12个字符,有11个连接,所以这66个中最多同时11个, 更直接就是 12!=479001600, 但是太大了
12如果是bitmask, 但似乎表示不了什么意义, 即使连成链, 不只是两头有意义, 中间的连接方式也会影响
所以其实是 n = 1000
然后每个字符串提供一个 <= 11 的连接方案(66种候选), 如果超过11一定不可能
然后 选一个集合, 使得连接方案的并 依然<= 11, 且没有任何字符连了3个, 也不构成环
样例1
就可以变形成
1 2 3 4
| 3 2 a-b a-c 2 a-b b-c 1 b-d
|
一个想法是, 从排列来讲是12!, 上面看到是4e8, 但是如果仅仅说连接方式, 一个连接方式 对应两个排列, 所以还是有2e8
难道真的要 暴力搜索+剪枝吗??
这个ci, 1e5 到大不小的,算和的话能有1e8, 没啥想法
想钦定一个 si被选, 但这样的 感觉还没钦定连接好, 但钦定连接就是枚举
题解
每个单词, 考虑12顶点的图, 然后根据相邻建立边, 注意到 一定是连通的!!!, (有道理啊 我还在想可能多个路径呢),
和我一样的分析, 每个最多连两个且不为环, 所以一定是path
从而把path变成string , 那么就只可能正着 或 倒着两种情况, 比如样例1里的 a-b a-c, 其实, 如果选它, 则t里,要么 bac,要么cab !
令这种 从初始si到字符串的转化为 f(si) 和 f’(si) (两个顺序
题意变成 找 ‘a’~’l’的排列串, 使得 它包含子串 f(si) 或 f’(si), 然后代价和最大
长度>1,所以 f和f’, 至多一个属于, 且至多在字符串中出现一次
所以变成 给一堆 [f(si), ci],[f’(si), ci]
找排列, 让包含子串 ci和最大
用AC自动机+dp
dp[mask characters,ac automaton state]
$O(2^K\cdot K\cdot A)$, K = size(12), A = size(state of automaon) ~ 4000
代码
https://codeforces.com/contest/1739/submission/189050459
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| #include <bits/stdc++.h> using namespace std; #define rep(i,a,n) for (int i=(a);i<(int)(n);i++) #define all(o) begin(o),end(o) int read(){int r;scanf("%d",&r);return r;} template<class T> void setMax(T&a,T b){a=max(a,b);}
char tmp[2010]; vector<int> s[1010]; const int INF=0x3f3f3f3f; const int N = 10000; const int K = 12;
int tsz = 0; struct acNode{ array<int,K> next; array<int,K> nnext; int ch; int fa; int lnk; int cost; int ncost; } node[N+10];
int newNode() { fill(all(node[tsz].next),-1); fill(all(node[tsz].nnext),-1); node[tsz].lnk = -1; node[tsz].ncost = -1; node[tsz].cost = 0; return tsz++; } const int root=newNode();
int next_state(int,int); int get_lnk(int x) { int& d = node[x].lnk; if(d != -1) return d; if(x == root || node[x].fa == root) return d = root; return d = next_state(get_lnk(node[x].fa), node[x].ch); }
int next_state(int x, int y) { int& d = node[x].nnext[y]; if(d != -1) return d; if(node[x].next[y] != -1) return d = node[x].next[y]; if(x == root) return d = root; return d = next_state(get_lnk(x), y); }
void add(const string &s, int c) { auto nxt=[&](int x, int y) { int &id=node[x].next[y]; if(id == -1) { id = newNode(); node[id].ch = y; node[id].fa = x; } return id; }; int cur = root; for(char x:s) cur = nxt(cur, x - 'a'); node[cur].cost += c; }
int calc(int x) { auto &c=node[x].ncost; if(c != -1) return c; c = node[x].cost; int y = get_lnk(x); if(y != x) c += calc(y); return c; }
int main() { int n = read(); rep(i,0,n){ string s; int x=read(); cin>>s; map<char, set<char>> adj; rep(j,0,size(s)-1){ adj[s[j]].insert(s[j+1]); adj[s[j+1]].insert(s[j]); } string res = ""; rep(c,'a','a'+K) if(adj[c].size()==1) { bool bad = false; res.push_back(c); while(adj[c].size() > 0) { if(adj[c].size() > 1) bad = true; char d = *adj[c].begin(); adj[c].erase(d); adj[d].erase(c); c = d; res.push_back(c); } if(bad) break; add(res, x); reverse(all(res)); add(res, x); break; } }
vector dp(1<<K, vector<array<int,3>>(tsz+1, {-INF,0,0})); dp[0][0] = {0,0,0}; rep(mask,0,1<<K)rep(state,0,tsz+1)if(dp[mask][state][0]!=-INF)rep(c,0,K)if(!(mask & (1<<c))) { int nstate = next_state(state, c); setMax(dp[mask|(1<<c)][nstate], {dp[mask][state][0]+calc(nstate),c,state}); }
{ int mask=(1<<K)-1; int state = max_element(all(dp[mask])) - dp[mask].begin(); while(mask) { auto [_,c,pstate] = dp[mask][state]; printf("%c",'a'+c); mask &= ~(1<<c); state = pstate; } } return 0; }
|
总结
F
明显的一定连通的结论, 想不到!???
ac自动机上做DP 第一次见到, 看懂了觉得好像也不太难, 但第一次见还是有点神奇, 感觉应该可以出个类似简单版本的 KMP+dp
不过看到多个字符串 应该向ac自动机去想
参考
官方
CF 1202 E 也用过AC自动机