[考试记录] 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
联通块数=剩余点数 - 剩余变数
那么贡献就可以拆成:点*点-边*点-点*边+边*边
那么就可以分成三种情况讨论:
- 点*点:\(u\in T\),\(v\in U\),当 \(u\not = v\) 均保留的概率为 \(\frac{1}{4}\),而 \(u=v\) 的概率为 \(0\)。所以期望为 \(\frac{n(n-1)}{4}\)。
- 边*点:选择 \((x,y)\in T\),\(u\in U\),\(x\not =u\and y\not = u\)并且均保留的概率为 \(\frac{1}{8}\),其余均为 \(0\)。所以期望为 \(\frac{(n-1)(n-2)}{8}\)。
- 边*边:选择 \((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 + 二分求出。
考虑下一步操作:增加和修改都会导致匹配长度加长,但是增加不会改变最大的 \(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;
}