A [NOIP2020] 字串匹配
列舉迴圈節 \(AB\),找到最多的迴圈節,剩下的一定包含在 \(C\)。然後可以發現的是 \(F(C)=F((AB)^kC),k>1\),那麼就只用考慮 \(C\) 和 \(ABC\) 的貢獻即可。複雜度 \(\mathcal{O}(N\log N)\)。
#include<bits/stdc++.h>
using namespace std;
constexpr int N = (1 << 20) + 5;
uint64_t h[N], p[N], cnt[26], pnum[N];
inline uint64_t geth(int l, int r){ return h[r] - h[l-1] * p[r-l+1]; }
namespace BIT{
#define lb(x) ((x) & (-x))
int t[30];
inline void add(int pos, int val){
for(; pos<=29; pos+=lb(pos)) t[pos] += val;
}
inline int query(int pos){
int res = 0;
for(; pos; pos-=lb(pos)) res += t[pos];
return res;
}
inline void clear(){ memset(t, 0, sizeof(t)); }
}
int main(){
p[0] = 1; for(int i=1; i<=1<<20; ++i) p[i] = p[i-1] * 131ull;
int T; cin>>T; while(T--){
string str; cin>>str; int n = str.size();
for(int i=1; i<=n; ++i) h[i] = h[i-1] * 131ull + str[i-1];
for(int i=n; i>=1; --i)
pnum[i] = (++cnt[str[i-1] - 'a'] & 1) ? pnum[i+1]+1 : pnum[i+1]-1;
memset(cnt, 0, sizeof(cnt));
long long ans = 0;
for(int i=2, num=0; i<n; ++i){
int pos = i;
while(pos+i < n && geth(pos+1, pos+i) == geth(1, i)) pos += i;
(++cnt[str[i-2] - 'a'] & 1) ? ++num : --num;
BIT::add(num+1, 1);
int nm = pos / i - 1, res1 = BIT::query(pnum[pos+1]+1), res2 = BIT::query(pnum[pos-i+1]+1);
ans += (nm / 2 + 1) * res1 + (nm - nm / 2) * res2;
} cout<<ans<<'\n';
BIT::clear(); memset(cnt, 0, sizeof(cnt));
memset(pnum, 0, sizeof(pnum));
}
}
B Short Code
建 Trie,dfs遍歷,每次把深度最深的節點提到當前節點。用優先佇列維護即可。
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
long long ans;
namespace Trie{
int t[N][26], cnt;
bitset<N> flag;
priority_queue<int> q[N];
inline void insert(string s){
int len = s.size(), id = 0;
for(int i=0; i<len; ++i){
int ch = s[i] - 'a';
if(!t[id][ch]) t[id][ch] = ++cnt;
id = t[id][ch];
} flag[id] = 1; q[id].emplace(len); ans += len;
}
inline void solve(int u, int dep){
for(int i=0; i<26; ++i) if(t[u][i]){
int v = t[u][i]; solve(v, dep + 1);
while(!q[v].empty()){
q[u].emplace(q[v].top());
q[v].pop();
}
}
if(u && !flag[u]){
ans -= q[u].top() - dep;
q[u].pop(); q[u].emplace(dep);
}
}
}
string str[N];
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n; cin>>n;
for(int i=1; i<=n; ++i) cin>>str[i], Trie::insert(str[i]);
Trie::solve(0, 0); return cout<<ans, 0;
}
C 串串題
考慮用 \(B\) 去匹配 \(A\),匹配出來的一定是一段區間,令 \(cnt\) 表示一段區間內非 \(B\) 包含的元素種類數,\(bcnt\) 表示 \(B\) 中元素種類數,貢獻即為 \(\binom{w-bcnt-cnt}{d-cnt}\)。預處理出所有可能匹配的區間即可。
#include<bits/stdc++.h>
using namespace std;
constexpr int M = 1e9 + 7, N = 1e6 + 5;
int a[N], b[N], nxt[N], fac[N], inv[N], val[N], pos[N], l[N], r[N], c[N];
bitset<N> vis;
inline int qpow(int a, int k){
int res = 1; while(k){
if(k & 1) res = (long long)res * a % M;
a = (long long)a * a % M; k >>= 1;
} return res;
}
inline int add(initializer_list<int> Add){
int res = 0;
for(int v : Add) res = res + v >= M ? res + v - M : res + v;
return res;
}
inline int mul(initializer_list<int> Mul){
int res = 1;
for(int v : Mul) res = (long long)res * v % M;
return res;
}
inline int C(int a, int b){ return b > a ? 0 : mul({fac[a], inv[b], inv[a-b]}); }
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
fac[0] = inv[0] = 1;
for(int i=1; i<N; ++i) fac[i] = mul({fac[i-1], i});
inv[N-1] = qpow(fac[N-1], M-2);
for(int i=N-2; i>=1; --i) inv[i] = mul({inv[i+1], i+1});
int T; cin>>T; while(T--){
int n, m, w, d, p = 0; cin>>n>>m>>w>>d;
for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=m; ++i) cin>>b[i], vis[b[i]] = 1;
for(int i=1; i<=n; ++i) if(vis[a[i]]) pos[++p] = i, c[p] = a[i];
for(int i=2, j=0; i<=m; ++i){
while(j && (b[i] ^ b[j+1])) j = nxt[j];
nxt[i] = j + (b[i] == b[j+1]); j = nxt[i];
} int ans = 0, q = 0; d += vis.count();
for(int i=1, j=0; i<=p; ++i){
while(j && (c[i] ^ b[j+1])) j = nxt[j];
if(j < m && c[i] == b[j+1]) ++j;
if(j == m) l[++q] = pos[i-m+1], r[q] = pos[i], j = nxt[j];
}
for(int i=1, j=1, k=1, cnt=0; i<=n && k<=q; ++i){
if(!val[a[i]]++) ++cnt;
if(r[k] == i){
while(j < l[k]) if(!--val[a[j++]]) --cnt; ++k;
if(d >= cnt) ans = add({ans, C(w-cnt, d-cnt)});
}
} cout<<ans<<'\n'; vis.reset();
for(int i=1; i<=n; ++i) val[a[i]] = 0;
} return 0;
}
D [COCI2016-2017#1] Cezar
考慮建出 Trie 樹,然後在每兩個字串不同的位置的字元按照最終排名建邊。然後跑拓撲。如果有環則 NE
,否則按照拓撲序輸出即可。注意特判字首的情況。
#include<bits/stdc++.h>
using namespace std;
string str[105];
int in[30000], rk[10005], ans[30000];
vector<int> G[30000];
queue<int> q;
namespace Trie{
#define p pair<int, int>
int t[10005][26], cnt, son[10005*26], ed[10005*26];
vector<p> vec[10005*26];
inline void insert(string s, int ai){
int id = 0, len = s.size();
for(int i=0; i<len; ++i){
int ch = s[i] - 'a';
if(!t[id][ch]) t[id][ch] = ++cnt, son[cnt] = 114;
vec[id].emplace_back(p{ai, ch});
son[id] = min(rk[ai], son[id]);
id = t[id][ch];
} ed[id] = rk[ai];
}
inline void solve(int u){
if(ed[u] && ed[u] > son[u]) cout<<"NE", exit(0);
if(vec[u].empty()) return;
sort(vec[u].begin(), vec[u].end(), [](p x, p y){
return rk[x.first] < rk[y.first];
});
for(int i=0; i<vec[u].size()-1; ++i) if(vec[u][i].second ^ vec[u][i+1].second){
G[vec[u][i].second].emplace_back(vec[u][i+1].second);
++in[vec[u][i+1].second];
}
for(int i=0; i<26; ++i) if(t[u][i]) solve(t[u][i]);
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n; cin>>n; Trie::son[0] = 114;
for(int i=1; i<=n; ++i) cin>>str[i];
for(int i=1, r; i<=n; ++i) cin>>r, rk[r] = i;
for(int i=1; i<=n; ++i) Trie::insert(str[i], i);
Trie::solve(0);
for(int i=0; i<26; ++i) if(!in[i]) q.emplace(i);
int cnt = 0; while(!q.empty()){
int u = q.front(); q.pop();
ans[u] = ++cnt;
for(int v : G[u]){
if(!in[v]) return cout<<"NE", 0;
if(!--in[v]) q.emplace(v);
}
}
auto check = [&](){
for(int i=0; i<26; ++i) if(in[i]) return false;
return true;
}; if(!check()) return cout<<"NE", 0;
cout<<"DA\n";
for(int i=0; i<26; ++i) cout<<char(ans[i]-1+'a');
return 0;
}
E [CSP-S 2023] 消消樂
用一個 pre
陣列維護最小 \(k\) 使得 \(k\sim i\) 合法。\(pre[i]=pre[pre[i]]-1\) 直到 \(pre[pre[i]]\) 為 \(0\),或者 \(S_i=S_{pre[i]}\)。維護 \(f_i\) 陣列表示以 \(i\) 為右端點的合法區間數量,若 \(pre_i\) 不為 \(0\) 那麼有 \(f_i=f_{pre_i-1}+1\),否則為 \(0\)。
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 2e6 + 5;
int pre[N], val[26]; long long f[N], sum;
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n; string s; cin>>n>>s;
for(int i=2; i<=n; ++i){
pre[i] = i - 1;
while(pre[pre[i]] && (s[i-1] ^ s[pre[i]-1])) pre[i] = pre[pre[i]] - 1;
s[i-1] == s[pre[i]-1] ? sum += f[i] = f[pre[i]-1] + 1 : f[i] = pre[i] = 0;
} return cout<<sum, 0;
}
F [CSP-S 2022] 星戰
科技 和雜湊。考慮到只要圖上所有點的出度都為 \(1\),那麼一定合法。維護出度可以做到 \(\mathcal{O}(nq)\)。考慮維護入度,維護入度是 \(\mathcal{O}(1)\) 的,並且圖上的入度和一定等於出度和,考慮到合法情況的出度和為 \(n\),那麼合法情況的入度和也一定為 \(n\),但是這 \(n\) 個出度是均勻分佈在每一個點上的,也就是這 \(n\) 個入度是由每一個點貢獻的,那麼給每一個點隨一個權值即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 5e5 + 5;
int in[N], val[N], f[N];
mt19937 mtrd(time(0));
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m; cin>>n>>m; int sum = 0;
for(int i=1; i<=n; ++i) val[i] = mtrd();
int bas = accumulate(val+1, val+1+n, 0ll);
for(int i=1, u, v; i<=m; ++i){
cin>>u>>v; f[v] += val[u];
in[v] += val[u], sum += val[u];
}
int q; cin>>q; while(q--){
int opt, u, v; cin>>opt;
if(opt == 1) cin>>u>>v, in[v] -= val[u], sum -= val[u];
else if(opt == 2) cin>>u, sum -= in[u], in[u] = 0;
else if(opt == 3) cin>>u>>v, in[v] += val[u], sum += val[u];
else cin>>u, sum += f[u] - in[u], in[u] = f[u];
cout<<(sum == bas ? "YES\n" : "NO\n");
} return 0;
}
F Comfortably Numb
首先將區間異或轉化為異或字首和,然後考慮分治處理區間。對於 \(l\sim r\),定義 \(L\in[l,mid],R\in[mid+1,r]\),分別規定最大值由左端點和右端點取得,雙指標維護即可。在 trie 樹上求最大異或即可。
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 2e5 + 5, T = 1e7 + 5;
int n, a[N], b[N], ans;
namespace Trie{
int t[T][2], cnt;
inline void insert(int x){
int id = 0, b = 0;
for(int i=30; i>=0; --i){
b = 1 & (x >> i);
if(!t[id][b]) t[id][b] = ++cnt;
id = t[id][b];
}
}
inline int query(int x){
if(!cnt) return 0;
int id = 0, b = 0, res = x;
for(int i=30; i>=0; --i){
b = (1 & (x >> i)) ^ 1;
if(t[id][b]) id = t[id][b], res ^= b << i;
else id = t[id][b^1], res ^= (b^1) << i;
} return res;
}
inline void clear(){
for(int i=0; i<=cnt; ++i) t[i][0] = t[i][1] = 0;
cnt = 0;
}
}
inline void mao(int l, int r){
if(l == r) return;
int mid = (l + r) >> 1;
for(int L=mid, R=mid, mx=a[mid]; L>=l; --L){
mx = max(mx, a[L]);
while(R < r && a[R+1] <= mx) ++R, Trie::insert(b[R]);
ans = max(ans, Trie::query(b[L-1] ^ mx));
} Trie::clear();
for(int R=mid+1, L=mid+1, mx=a[mid+1]; R<=r; ++R){
mx = max(mx, a[R]);
while(L > l && a[L-1] <= mx) --L, Trie::insert(b[L-1]);
ans = max(ans, Trie::query(b[R] ^ mx));
} Trie::clear();
mao(l, mid), mao(mid+1, r);
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T; cin>>T; while(T--){
cin>>n; ans = 0;
for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n; ++i) b[i] = b[i-1] ^ a[i];
mao(1, n); cout<<ans<<'\n';
} return 0;
}
H Hossam and Range Minimum Que
給每個點隨一個權值,找到區間異或不為 \(0\) 的點即可。主席樹維護。
#include<bits/stdc++.h>
using namespace std;
#define int unsigned int
constexpr int N = 2e5 + 5;
int a[N], b[N], rt[N], val[N], pos[N];
mt19937 mtrd(time(0));
namespace ST{
struct node{ int l, r, ls, rs, val; }t[N*30];
int cnt;
inline void build(int &id, int l, int r){
id = ++cnt; t[id].l = l, t[id].r = r;
if(l == r) return;
int mid = (l + r) >> 1;
build(t[id].ls, l, mid), build(t[id].rs, mid+1, r);
}
inline void modify(int &id, int pre, int pos, int val){
t[id = ++cnt] = t[pre];
if(t[id].l == t[id].r) return t[id].val ^= val, void();
int mid = (t[id].l + t[id].r) >> 1;
if(pos <= mid) modify(t[id].ls, t[pre].ls, pos, val);
else modify(t[id].rs, t[pre].rs, pos, val);
t[id].val = t[t[id].ls].val ^ t[t[id].rs].val;
}
inline int query(int x, int y){
if(!x || !y) return 0;
int val = t[x].val ^ t[y].val;
if(!val) return 0;
if(t[x].l == t[x].r) return t[x].l;
if(t[t[x].ls].val ^ t[t[y].ls].val) return query(t[x].ls, t[y].ls);
else return query(t[x].rs, t[y].rs);
}
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n; cin>>n;
for(int i=1; i<=n; ++i) cin>>a[i], b[i] = a[i];
sort(b+1, b+1+n); int len = unique(b+1, b+1+n) - b - 1;
for(int i=1; i<=n; ++i) pos[i] = lower_bound(b+1, b+1+len, a[i]) - b;
ST::build(rt[0], 1, len);
for(int i=1; i<=len; ++i) val[i] = mtrd();
for(int i=1; i<=n; ++i) ST::modify(rt[i], rt[i-1], pos[i], val[pos[i]]);
int q, ans = 0; cin>>q; while(q--){
int l, r; cin>>l>>r;
l ^= ans, r ^= ans;
ans = b[ST::query(rt[l-1], rt[r])];
cout<<ans<<'\n';
} return 0;
}