Welcome to the Ni|

XiaoLe_MC

园龄:1年2个月粉丝:3关注:9

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

基础暴力分 300pts 🤡

T1 图书管理

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

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

极限数据可以卡到 O(N3)

#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. 点*点:uTvU,当 uv 均保留的概率为 14,而 u=v 的概率为 0。所以期望为 n(n1)4
  2. 边*点:选择 (x,y)TuUxuyu并且均保留的概率为 18,其余均为 0。所以期望为 (n1)(n2)8
  3. 边*边:选择 (x,y)T(u,v)U,当 x,y,u,v互不相同时,概率为 116,其余均为 0。那么枚举 T 中的边,计算符合条件的 U 中的边:n1 减去 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 函数

N2 过百万,暴力碾标算

歪解。

考虑两种暴力:

  • 从左向右枚举每一个数检查即可,复杂度 O(N)
  • 枚举 B0 每一个值,将这个值异或 A 之后看存不存在,如果存在则判断这个数的前一位或者后一位异或 A 是否大于 B 即可。复杂度 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(n2) 枚举所有情况。

60pts

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

100pts

求解 xia 最小和最大的两个位置 c1,c2,如果 f(c1)×f(c2)>0 显然无解。

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

求解 xia 最小和最大的两个位置 c1,c2 可以利用 trie 快速求解,时间复杂度为 ((n+q)(logn+logV))

#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,那么令 fi,j 表示最大的 x,使得 S[1:x]T[l:l+x+j] 可以在 i 次编辑内得到,那么这样编辑形成的匹配,它的编辑距离一定是最短的。

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

fi,j=fi,j+LCT(fi,j+1,l+fi,j+j)

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

统计答案时就统计 xlenS 的状态数,注意判断是否合法。

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

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

本文作者:XiaoLe_MC

本文链接:https://www.cnblogs.com/xiaolemc/p/18533849

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   XiaoLe_MC  阅读(6)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起