W
e
l
c
o
m
e
: )

[考试记录] 2024.11.7 noip模拟赛7

基础暴力分 300pts 🤡

T1 图书管理

枚举每个左端点,维护一个中位数指针和一个桶,每次 push 进来两个数:

  • 两个数都大于当前中位数:中位数增大
  • 两个数都小于当前中位数:中位数减小
  • 两个数一大一小:中位数不变

极限数据可以卡到 \(\mathcal{O}(N^3)\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 1e4 + 5;
int n, p[N], val[N], ans;
signed main(){
	freopen("book.in", "r", stdin); freopen("book.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n; for(int i=1; i<=n; ++i) cin>>p[i];
	for(int i=1; i<=n; ++i){
		int mid = 0;
		for(int j=i; j<=n; j+=2){
			++val[p[j]];
			if(j == i){
				mid = p[j];
				ans += i * j * mid;
			} else {
				++val[p[j-1]];
				if(p[j-1] < mid && p[j] < mid){
					--mid;
					while(!val[mid]) --mid;
				} else if(p[j-1] > mid && p[j] > mid){
					++mid;
					while(!val[mid]) ++mid;
				}
				ans += i * j * mid;
			}
		}
		for(int j=i; j<=n; ++j) val[p[j]] = 0;
	} cout<<ans; return 0;
}

T2 两棵树

暴力 + 输出大样例 = 40pts

联通块数=剩余点数 - 剩余变数

那么贡献就可以拆成:点*点-边*点-点*边+边*边

那么就可以分成三种情况讨论:

  1. 点*点:\(u\in T\)\(v\in U\),当 \(u\not = v\) 均保留的概率为 \(\frac{1}{4}\),而 \(u=v\) 的概率为 \(0\)。所以期望为 \(\frac{n(n-1)}{4}\)
  2. 边*点:选择 \((x,y)\in T\)\(u\in U\)\(x\not =u\and y\not = u\)并且均保留的概率为 \(\frac{1}{8}\),其余均为 \(0\)。所以期望为 \(\frac{(n-1)(n-2)}{8}\)
  3. 边*边:选择 \((x,y)\in T\)\((u,v)\in U\),当 \(x,y,u,v\)互不相同时,概率为 \(\frac{1}{16}\),其余均为 \(0\)。那么枚举 \(T\) 中的边,计算符合条件的 \(U\) 中的边:\(n-1\) 减去 \(u\) 连接的边数和 \(v\) 连接的边数,如果存在 \((x,y)=(u,v)\) 则加上一。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
constexpr int N = 2e5 + 5, M = 998244353, Inv2 = 499122177, Inv16 = 935854081;
int n, ans, g[N];
unordered_map<ll, bool> mp;
struct edge{ int u, v; }e[N];
inline int qpow(int a, int k){
	int res = 1; while(k){
		if(k & 1) res = (ll)res * a % M;
		a = (ll)a * a % M; k >>= 1;
	} return res;
}
inline int mul(initializer_list<int> Mul){
	int res = 1;
	for(int v : Mul) res = (ll)res * v % M;
	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 mod(int x){ return (x % M + M) % M; }
int main(){
	freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n; 
	for(int i=1; i<n; ++i) cin>>e[i].u>>e[i].v;
	for(int i=1, u, v; i<n; ++i){
		cin>>u>>v, ++g[u], ++g[v];
		if(u > v) swap(u, v);
		mp[(ll)u*N+(ll)v] = 1;
	}
	ans = mul({n-1, Inv2});
	for(int i=1; i<n; ++i){
		int u = e[i].u, v = e[i].v;
		if(u > v) swap(u, v);
		ans = add({ans, mul({add({n-1, -g[u], -g[v], mp[(ll)u*N+(ll)v]}), Inv16})});
	} return cout<<ans, 0;
}

T3 函数

\(N^2\) 过百万,暴力碾标算

歪解。

考虑两种暴力:

  • 从左向右枚举每一个数检查即可,复杂度 \(\mathcal{O}(N)\)
  • 枚举 \(B\sim 0\) 每一个值,将这个值异或 \(A\) 之后看存不存在,如果存在则判断这个数的前一位或者后一位异或 \(A\) 是否大于 \(B\) 即可。复杂度 \(\mathcal{O}(B)\)

结合起来就能艹过去,并且最优解。数据太水

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e6 + 5;
int n, q, a[N];
unordered_map<int, int> mp;
int main(){
	freopen("fun.in", "r", stdin); freopen("fun.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>q; for(int i=1; i<=n; ++i) cin>>a[i], mp[a[i]] = i;
	int A, B; while(q--){
		cin>>A>>B; bool ok = 1;
		if(n <= B){
			for(int i=1; i<n; ++i) if(((a[i]^A)<=B && (a[i+1]^A)>=B) || ((a[i]^A)>=B && (a[i+1]^A)<=B))
				{ cout<<i<<'\n'; ok = 0; break; }
		} else {
			for(int i=0; i<=B; ++i){
				int id = mp[i^A];
				if(!id) continue;
				if(id < n && (a[id+1] ^ A) >= B){
					cout<<id<<'\n'; ok = 0; break;
				}
				if(id > 1 && (a[id-1] ^ A) >= B){
					cout<<(id-1)<<'\n'; ok = 0; break;
				}
			}
		}
		if(ok) cout<<"-1\n";
	} return 0;
}

正解

30pts

\(O(n^2)\) 枚举所有情况。

60pts

通过整体二分,判定 \([1,mid]\) 内是否有解的方式找到第一组解,或利用离线数据结构技巧进行求解,复杂度为 \(O(n \log n \log C)\),与正解关联性不大。

100pts

求解 \(x_i \oplus a\) 最小和最大的两个位置 \(c_1,c_2\),如果 \(f(c_1) \times f(c_2) > 0\) 显然无解。

否则每次取 \(c_1,c_2\) 中点 \(mid\),因为 \(f(c_1),f(c_2)\) 异号,所以 \(f(c_1),f(mid)\)\(f(mid),f(c_2)\) 必然有一对异号,每次区间长度减半,因此重复 \(\log\) 次即可。

求解 \(x_i \oplus a\) 最小和最大的两个位置 \(c_1,c_2\) 可以利用 trie 快速求解,时间复杂度为 \(((n+q) (\log n + \log V))\)

#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e6 + 5, T = 3e7 + 5;
int n, q, li[N];
namespace Trie{
	int Id[T], sn[T][2], cnt;
	inline void insert(int x, int id){
		int p = 0; bool b = 0;
		for(int i=29; i>=0; --i){
			b = (x >> i) & 1;
			if(!sn[p][b]) sn[p][b] = ++cnt;
			p = sn[p][b];
		} Id[p] = id;
	}
	inline int query(int a, int b){
		int p1 = 0, p2 = 0, l, r; bool bt = 0;
		for(int i=29; i>=0; --i){
			bt = (a >> i) & 1;
			
			if(sn[p1][bt]) p1 = sn[p1][bt];
			else p1 = sn[p1][bt^1];
			
			if(sn[p2][bt^1]) p2 = sn[p2][bt^1];
			else p2 = sn[p2][bt];
			
		} l = Id[p1], r = Id[p2];
		if(l > r) swap(l, r);
		int numl = (li[l] ^ a) - b, numr = (li[r] ^ a) - b;
		if((long long)numl * numr > 0) return -1;
		if(abs(l - r) == 1) return min(l, r);
		int mid, numd;
		while(l < r-1){
			mid = (l + r) >> 1, numd = (li[mid] ^ a) - b;
			if((long long)numd * numl > 0) l = mid, numl = numd;
			else r = mid, numr = numd;
		} return l;
	}
}
int main(){
	freopen("fun.in", "r", stdin); freopen("fun.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>q; for(int i=1; i<=n; ++i)
		cin>>li[i], Trie::insert(li[i], i);
	int a, b;
	while(q--) cin>>a>>b, cout<<Trie::query(a, b)<<'\n';
	return 0;
}

编辑

不可以进行修改,然后获得 60pts……🤬

考虑枚举 \(T\) 的每一个后缀,令左端点为 \(l\),那么令 \(f_{i,j}\) 表示最大的 \(x\),使得 \(S[1:x]\)\(T[l:l+x+j]\) 可以在 \(i\) 次编辑内得到,那么这样编辑形成的匹配,它的编辑距离一定是最短的。

那么考虑转移。既然要找到最大的 \(x\),那么可以利用 Hash + 二分求出。

\[f_{i,j}=f_{i,j}+\text{LCT}(f_{i,j}+1,l+f_{i,j}+j) \]

考虑下一步操作:增加和修改都会导致匹配长度加长,但是增加不会改变最大的 \(x\) 的位置,但是修改会使最大的 \(x\) 加一。对于删除,\(x\) 是会减小的,但是为了方便统计答案,所以不减。

统计答案时就统计 \(x\ge len_S\) 的状态数,注意判断是否合法。

最后统计的答案其实是个前缀和状物,减一下即可。

#include<bits/stdc++.h>
using namespace std;
#define ull uint64_t
constexpr int N = 5e4 + 5, Inf = -1e9;
int k, lens, lent, ans[35], f[35][65];
string S, T;
ull p[N], hs[N], ht[N];
inline ull geth(ull *h, int l, int r){ return h[r] - h[l-1] * p[r-l+1]; }
inline int lct(int l1, int l2){
	if(l1 < 0 || l2 < 0 || l1 > lens || l2 > lent || S[l1-1] ^ T[l2-1]) return 0;
	int l = 0, r = min(lens-l1, lent-l2), mid;
	while(l < r){
		mid = (l + r + 1) >> 1;
		if(geth(hs, l1, l1+mid) ^ geth(ht, l2, l2+mid)) r = mid - 1;
		else l = mid;
	} return l + 1;
}
int main(){
	freopen("edit.in", "r", stdin); freopen("edit.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>k>>S>>T; p[0] = 1; lens = S.size(), lent = T.size();
	for(int i=1; i<=lent; ++i) p[i] = p[i-1] * 131;
	for(int i=1; i<=lens; ++i) hs[i] = hs[i-1] * 131 + S[i-1] - 'a';
	for(int i=1; i<=lent; ++i) ht[i] = ht[i-1] * 131 + T[i-1] - 'a';
	for(int l=1; l<=lent; ++l){
		for(int i=0; i<=k; ++i) for(int j=0; j<=k<<1; ++j) f[i][j] = Inf;
		f[0][k] = 0;
		for(int i=0; i<=k; ++i) for(int j=-k; j<=k; ++j) if(f[i][j+k] ^ Inf){
			f[i][j+k] += lct(f[i][j+k]+1, l+f[i][j+k]+j);
			if(f[i][j+k] >= lens && j+lens > 0 && j+lens <= lent-l+1) ++ans[i];
			f[i+1][j+k] = max(f[i+1][j+k], f[i][j+k]+1);
			if(j != -k) f[i+1][j+k-1] = max(f[i+1][j+k-1], f[i][j+k]+1);
			if(j != k)  f[i+1][j+k+1] = max(f[i+1][j+k+1], f[i][j+k]);
		}
	}
	for(int i=0; i<=k; ++i) cout<<(ans[i]-ans[i-1])<<'\n';
	return 0;
}
posted @ 2024-11-07 19:50  XiaoLe_MC  阅读(18)  评论(0)    收藏  举报