提高組字串專題1

XiaoLe_MC發表於2024-11-23

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;
}

相關文章