21 ICPC 区域赛 沈阳 EFBJM 题解
很有意思的比赛,尤其是顺势吃了去年的瓜
暂时补了 \(EFBJM\)
E
签到
solution
判断 \(|t| = 5\) 的串在模式串出现次数,暴力枚举即可。
F
签到
solution
因为 \(n \le 10^3\),所以 \(\sum_{i=1}^n |s[i,n]| \le 10^6\)
我们暴力对所有前缀编码,然后取最值即可。
B
仍是暴力
solution
因为 \(\oplus\),所以各个位是独立的,那么我们对每个位去进行 dfs 即可。
对于一个位,不同联通块直接不产生影响,一个联通快构成一个图。
那么对其中任意一个元素设置初始值,块内其他元素的值就都可以确定了。
又因为初值可以设置 \(0/1\),那么
- 如果都不冲突,则取较小值
- 都冲突,则无解,输出
-1 - 一个可行则直接加入答案。
在 gym 上这题给了 \(10s\) 不知道赛场是否相同。
我的代码应该还算常数比较大比较丑陋了。
code
int n,m,op1,op2;
vector<PII> v[N];
int a[N][2][D];
bool vis[N][2][D];
int dfs(int p,int b,int k,int &op)
{
int sum = a[p][b][k];
for(auto &[y,w] : v[p])
{
int t = (w>>k)&1;
if(vis[y][b][k])
{
if(a[y][b][k] != (a[p][b][k]^t)) op = 0;
continue;
}
vis[y][b][k] = true;
a[y][b][k] = a[p][b][k]^t;
sum += dfs(y,b,k,op);
}
return sum;
}
void func(void)
{
cin >> n >> m;
for(int i=1;i<=m;++i)
{
int x,y,w;
cin >> x >> y >> w;
v[x].push_back({y,w});
v[y].push_back({x,w});
}
int ans = 0;
for(int i=0;i<30;++i)
{
for(int j=1;j<=n;++j)
{
if(!vis[j][0][i])
{
int res = M;
op1 = op2 = 1;
a[j][1][i] = 1, a[j][0][i] = 0;
vis[j][0][i] = true;
int tmp = dfs(j,0,i,op2);
if(op2) res = min(res,tmp);
vis[j][1][i] = true;
tmp = dfs(j,1,i,op1);
if(op1) res = min(res,tmp);
if(op1 || op2) ans += (1<<i)*res;
else
{
cout << "-1\n";
return;
}
}
}
}
cout << ans << '\n';
}
J
开始准备模拟,然后想能不能暴力,就写了个bfs
sloution
对于 \(1234 \rightarrow 2345\) 和 \(0000 \rightarrow 1111\) 是一样的,或者说本质不同状态只有 \(10^5\) 种。
那么我们从 \(0000\) 开始跑 bfs 即可。
出于方便处理,用用 vector 处理状态了,而没有用数字。
code
map<vector<int>,int> ans;
void func(void);
signed main(void)
{
Start;
int _ = 1;
queue<pair<vector<int>,int>> q;
vector<int> t(4);
q.push({t,0});
while(q.size())
{
auto [x,y] = q.front(); q.pop();
if(ans.count(x)) continue;
ans[x] = y;
for(int i=0;i<4;++i)
{
for(int j=i;j<4;++j)
{
vector<int> t1 = x,t2 = x;
for(int k=i;k<=j;++k)
{
t1[k] = (t1[k]+1)%10;
t2[k] = (t2[k]+9)%10;
}
if(!ans.count(t1)) q.push({t1,y+1});
if(!ans.count(t2)) q.push({t2,y+1});
}
}
}
cin >> _;
while(_--) func();
return 0;
}
void func(void)
{
string s1,s2;
cin >> s1 >> s2;
vector<int> a(4);
for(int i=0;i<4;++i) a[i] = (s1[i]-s2[i]+10)%10;
cout << ans[a] << '\n';
}
M
看到字符串起手就敲了个sa,然后发现没那么简单。
sam学的不好也没想出来。
最后看这篇题解,大受震撼。
ICPC2021 沈阳站 M String Problem 题解 | 十种做法一网打尽 , 一道题带你回顾字符串科技
真的太强了,要不是这个博客,我可能就只是按官解用sam或border补了
后缀数组
solution1
明显可以用 \(sa\),但是直接用 \(sa\) 明显不太可能。
答案是各个前缀的后缀,因为如果是前缀的子串,那么无脑延长为后缀肯定比用空串要大。
那么答案是各个前缀的后缀,但是后缀数组存储整个串的后缀。
对于串 \(babc\),在 \(i = 4\) 时,\(bc > babc\),也就是 \(rk_3 > rk_1\)
但是在 \(i = 3\) 时,\(bab > b\),这时不能直接使用 \(rk\) 数组。
所以不能再访问到 \(i\) 时直接用 \(i\) 来更新答案。
我们继续看 \(babc\),在 \(i \in [1,3]\) 时,答案都是 \(s[1,i]\),而在 \(i = 4\) 时,我们用 \(rk_3\) 更新了答案,因为对于 \(babc\) 和 \(bc\),在第二位才出现不同,或者应该说是 \(lcp+1\) 出现了,不同。
对于新位置 \(i\) 和旧答案下标 \(p\),\(rk_i > rk_p\) 时,在 \(i+lcp(i,p)\) 打上标记,然后在访问到 \(i+lcp(i,p)\) 时,将答案更新为 \(i\)。
这时就又有个问题了,如果在 \(i \sim i+lcp\) 的过程中 \(p\) 被更新了怎么办?
那么进行讨论,设新位置 \(p'\),\(j\) 为当前访问下标,\(i\) 为在 \(j\) 打上的标记。
- 如果 \(rk_i < rk_{p'}\),那么继续使用 \(p\) 即可,无需更新。
- 否则只需要进行上面对于 \(i,p\) 相同的处理,只是这里处理的对象变为标记和新答案。
- 如果 \(lcp(p',i)\) 没有超过 \(j\),也就是 \(i + lcp(i,p') \le j\) 那么就是在 \(lcp\) 之后位置出现了不同,这时需要更新答案为 \(i\)。
- 如果 \(lcp(p',i)\) 超过了 \(j\),也就是还在 \(lcp\) 范围内,答案不需要更新,只是需要把新位置的标记更新。
solution2
吸取了一下上面博客的优化解法。
\(rk_1\) 我们肯定会使用,然后可能会使用第一个 \(> rk_1\) 的后缀,以此类推。
那么答案的 \(rk\) 就是递增的。
那么我们只需要比较相邻可能答案,他们的 \(lcp\) 是否会超过当前 \(i\) 即可。
code1
struct SA
{
int L;
vector<int> sa,rk,h,logk;
vector<vector<int>> mn;
SA(string& st)
{
L = st.size()-1;
sa.resize(L+1), rk.resize(L+1), h.resize(L+1);
build_sa(st), build_h(st);
}
void build_sa(const string &st)
{
int m = D, p = 0, len = max(m,L)+1;
vector<int> cnt(len), lrk(len), lsa(len);
for(int i=1;i<=L;++i) ++ cnt[rk[i] = (int)st[i]];
for(int i=1;i<=m;++i) cnt[i] += cnt[i-1];
for(int i=L;i>=1;--i) sa[cnt[rk[i]] --] = i;
for(int w=1;p!=L;w<<=1,m=p)
{
int idx = 0;
for(int i=L-w+1;i<=L;++i) lsa[++ idx] = i;
for(int i=1;i<=L;++i)
if(sa[i] > w) lsa[++ idx] = sa[i] - w;
fill(cnt.begin(),cnt.end(),0);
for(int i=1;i<=L;++i) ++ cnt[rk[i]];
for(int i=1;i<=m;++i) cnt[i] += cnt[i-1];
for(int i=L;i>=1;--i)
sa[cnt[rk[lsa[i]]] --] = lsa[i];
p = 0;
for(int i=1;i<=L;++i) lrk[i] = rk[i];
for(int i=1;i<=L;++i)
{
if(lrk[sa[i]] == lrk[sa[i-1]] &&
lrk[sa[i]+w] == lrk[sa[i-1]+w]) rk[sa[i]] = p;
else rk[sa[i]] = ++ p;
}
}
}
void build_h(const string &st)
{
sa[0] = rk[0] = 0;
for(int i=1,k=0;i<=L;++i)
{
if(k) k --;
while(st[i+k] == st[sa[rk[i]-1]+k]) ++ k;
h[rk[i]] = k;
}
logk.resize(L+1);
logk[1] = 0;
for(int i=2;i<=L;++i) logk[i] = logk[i/2]+1;
int k = logk[L]+1;
mn.resize(k,vector<int>(L+1));
for(int i=2;i<=L;++i) mn[0][i] = h[i];
for(int i=1;i<k;++i)
{
for(int j=2;j+(1<<i)-1<=L;++j)
{
mn[i][j] = min(mn[i-1][j],mn[i-1][j+(1<<(i-1))]);
}
}
}
int query_min(int l,int r)
{
int k = logk[r-l+1];
return min(mn[k][l],mn[k][r-(1<<k)+1]);
}
int lcp(int i,int j)
{
if(i == j) return L-i+1;
int rki = rk[i], rkj = rk[j];
if(rki > rkj) swap(rki,rkj);
return query_min(rki+1,rkj);
}
};
void func(void)
{
string st; cin >>st;
st = '_' + st;
SA sa(st);
int p = 1;
vector<int> tp(sa.L+1);
for(int i=1;i<=sa.L;++i)
{
if(sa.rk[i] > sa.rk[p]) tp[i+sa.lcp(i,p)] = i;
if(sa.rk[tp[i]] > sa.rk[p])
{
if(tp[i]+sa.lcp(p,tp[i]) <= i) p = tp[i];
else tp[tp[i]+sa.lcp(p,tp[i])] = tp[i];
}
cout << p << ' ' << i <<'\n';
}
}
code2
void func(void)
{
string st; cin >>st;
st = '_' + st;
SA sa(st);
int p = 0;
vector<int> tp(1,1);
for(int i=2;i<=sa.L;++i)
if(sa.rk[i] > sa.rk[tp.back()]) tp.push_back(i);
for(int i=1;i<=sa.L;++i)
{
while(p+1 < tp.size() && tp[p+1] + sa.lcp(tp[p],tp[p+1]) <= i) ++ p;
cout << tp[p] << ' ' << i << '\n';
}
}
后缀自动机
没用官解的在线,感觉离线还是简单些
solution
使用 DAWG 而非 link 树。
构造出 sam 后,我们从根节点遍历 DAWG,每次贪心最大字符路径。
那么构成的串,在截止位置上必然是字典序最大的。
但是对于 \(baba\),怎么保证 \(i = 4\) 的截止位置使用 \(baba\) 而非 \(ba\) 呢?
只需要给节点标记最早的下标,这样可以每个串使用最晚的标记,很明显在前缀相同时,串越长字典序越大。
code
int ans[N],ed[N];
string st;
bitset<N> vis;
// sam
int nxt[N][D],lk[N],len[N];
int rt = 1,lst = 1,idx = 1;
void extend(char c,int i)
{
int p = lst, u = ++ idx, t = c-'a';
lst = u, len[u] = len[p]+1;
ed[u] = i;
while(p && (!nxt[p][t]))
{
nxt[p][t] = u;
p = lk[p];
}
if(!p) lk[u] = rt;
else
{
int np = nxt[p][t];
if(len[p]+1 == len[np]) lk[u] = np;
else
{
int tp = ++ idx;
memcpy(nxt[tp],nxt[np],sizeof(nxt[np]));
len[tp] = len[p]+1, lk[tp] = lk[np];
ed[tp] = ed[np];
lk[np] = lk[u] = tp;
while(p && nxt[p][t] == np)
{
nxt[p][t] = tp;
p = lk[p];
}
}
}
}
void dfs(int p,int l)
{
vis[p] = true;
for(int i=D-1;i>=0;--i)
{
if(vis[nxt[p][i]]) continue;
dfs(nxt[p][i],l+1);
}
if(!ans[ed[p]]) ans[ed[p]] = ed[p]-l+1;
}
void func(void)
{
cin >> st;
st = '_' + st;
int L = st.size()-1;
for(int i=1;i<=L;++i) extend(st[i],i);
dfs(1,0);
for(int i=1;i<=L;++i) cout << ans[i] << ' ' << i << '\n';
}

浙公网安备 33010602011771号