『模拟赛』多校A层冲刺NOIP2024模拟赛19

Rank

byd CSP 之后就没场切过题😡😡😡

image

A. 图书管理

签,又寄了。

这种题直接做复杂度算着不对的话大概率就是要拆分贡献了。赛时用对顶堆维护的中位数,卡常到极致在 \(n=10^4\) 时要跑 1.2s。

感觉卡常有用所以写下来:发现如果每次新开一个堆结构最多只有 500500 个数,加上 STL 特性大概率会 RE,但是距离极限不大,所以可以对一个常数 \(\frac{3n}{4}\) 取模开这些数量的堆。发现每次清空是个耗时间的工作,但是左端点倒序枚举就能减少很多次可避免的删除操作。

发现对效率提升没用的:手动 O3;优先队列改成 set;register;快写。

那么考虑正解。对于每一位上的数统计取其值作为中位数的贡献之和。考虑将值大于当前值的数看作 1,小于的看作 -1,那么当某个区间和为 0 时即为当前值作为中位数的情况。考虑先向前扫,记录每个和的左端点位置之和(原因考虑乘法结合律);然后向右扫,为贡献加上 当前和的相反数的记录值 与 当前右端点位置 的积。最后用贡献乘上这个值即为当前点的贡献。复杂度 \(\mathcal{O(n^2)}\),完全不用卡常。

点击查看代码
#include<bits/stdc++.h>
#pragma GCC optimize(3)
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
    char ch = getchar(); lx x = 0, f = 1;
    for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
    return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
#define int ll
const int Ratio = 0;
const int N = 1e5 + 5;
const int mod = 1e9 + 7;
int n;
int a[N], bz[N];
ll ans;
int v[N << 1];
namespace Wisadel
{
    short main()
    {
        freopen("book.in", "r", stdin), freopen("book.out", "w", stdout);
        n = qr;
        fo(i, 1, n) a[i] = qr;
        fo(i, 1, n)
        {
            fill(v + 1, v + 1 + 2 * n, 0);
            bz[i] = 0;
            fo(j, 1, n) if(j != i) bz[j] = (a[j] > a[i]) ? 1 : -1;
            ll sum = 0, res = 0;
            fu(j, i, 1) sum += bz[j], v[sum + n] += j;
            sum = 0;
            fo(j, i, n) sum += bz[j], res += 1ll * v[n - sum] * j;
            ans += res * a[i];
        }
        printf("%lld\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

B. 两棵树

结论题。

结论:连通块树 = 点数 - 边数。那么就可以将所求转化:

\[X\times Y=(V_T-E_T)\times (V_U-E_U)=V_T\times V_U-V_T\times E_U-V_U\times E_T+E_T\times E_U \]

分别讨论。

考虑 \(u\in V_T,v\in V_U\),当 \(u\neq v\) 时有贡献,概率为 \(\frac{1}{4}\),否则贡献为 0;总期望 \(\frac{n(n-1)}{4}\)

考虑 \(u\in V_T,v\in E_U\),当 \(u\)\(v\) 的两个端点均不同时有贡献,概率为 \(\frac{1}{8}\),否则贡献为 0;总期望为 \(\frac{(n-1)(n-2)}{8}\)

考虑 \(u\in E_T,v\in E_U\),当四个端点均不相同时有贡献,概率为 \(\frac{1}{16}\),否则贡献为 0。枚举 \(T\) 中所有边,符合条件的边数量为 \(n-1-degU_u-degU_v+[(u,v)\in E_T\ \operatorname{and}\ (u,v)\in E_U]\)。怎么想?删去与当前边端点所有有关的边后剩余边数。

时间复杂度 \(\mathcal{O(n)}\)

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
    char ch = getchar(); lx x = 0, f = 1;
    for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
    return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
#define int ll
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 998244353;
int n;
int ds[N];
vector<pii> e;
map<pii, int> mp;
ll ans;
namespace Wisadel
{
    ll Wqp(ll x, int y)
    {
        ll res = 1;
        while(y){if(y & 1) res = res * x % mod; x = x * x % mod; y >>= 1;}
        return res;
    }
    short main()
    {
        freopen("tree.in", "r", stdin), freopen("tree.out", "w", stdout);
        n = qr;
        fo(i, 1, n - 1)
        {
            int a = qr, b = qr;
            if(a > b) e.P_B(M_P(b, a));
            else e.P_B(M_P(a, b));
        }
        fo(i, 1, n - 1)
        {
            int a = qr, b = qr;
            ds[a]++, ds[b]++;
            pii zc = a > b ? M_P(b, a) : M_P(a, b);
            mp[zc] = 1;
        }
        ans = 1ll * n * (n - 1) % mod * Wqp(4, mod - 2) % mod;
        ans = (ans - 1ll * (n - 1) * (n - 2) % mod * Wqp(4, mod - 2) % mod + mod) % mod;
        ll zc = 0;
        fo(i, 0, n - 2) zc = (zc + n - 1 - ds[e[i].fi] - ds[e[i].se] + mp[M_P(e[i].fi, e[i].se)] + mod) % mod;
        ans = (ans + zc * Wqp(16, mod - 2) % mod) % mod;
        printf("%lld\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

C. 函数

思维好题。

看到异或就应该想着点 trie 树。我们发现题目只要求出一组解即可,且有无解的判断依据只有正负两种。那么就可以想到一种神奇的思路。判断有无解最好办法是找极值,我们找出和 \(a\) 异或后最大和最小的两个值,如果他们都无法满足要求那么一定无解。否则 \(f(l),f(r)\) 一定异号,我们直接对它二分,\(f(mid)\) 一定与其中一个异号,不断操作直到 \(l+1=r\) 得出答案。时间复杂度 \(\mathcal{O(q(\log w+\log n))}\)

由于我们只需找出一组解,所以只用映射每个值出现位置的其中一个即可。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
    char ch = getchar(); lx x = 0, f = 1;
    for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
    return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 1e6 + 5, M = 2e7 + 5;
const int mod = 998244353;
int n, q;
int a[N];
int ch[M][2], cnt;
unordered_map<int, int> mp;
namespace Wisadel
{
    void Wins(int x)
    {
        int now = 0;
        fu(i, 30, 0)
        {
            int to = (x >> i) & 1;
            if(!ch[now][to]) ch[now][to] = ++cnt;
            now = ch[now][to];
        }
    }
    int Wqma(int x)
    {
        int now = 0, res = 0;
        fu(i, 30, 0)
        {
            int to = ((x >> i) & 1) ^ 1;
            if(ch[now][to]) now = ch[now][to], res = res * 2 + to;
            else now = ch[now][(to ^ 1)], res = res * 2 + (to ^ 1);
        }
        return res;
    }
    int Wqmi(int x)
    {
        int now = 0, res = 0;
        fu(i, 30, 0)
        {
            int to = (x >> i) & 1;
            if(ch[now][to]) now = ch[now][to], res = res * 2 + to;
            else now = ch[now][(to ^ 1)], res = res * 2 + (to ^ 1);
        }
        return res;
    }
    short main()
    {
        freopen("fun.in", "r", stdin), freopen("fun.out", "w", stdout);
        n = qr, q = qr;
        fo(i, 1, n) a[i] = qr, Wins(a[i]), mp[a[i]] = i;
        fo(i, 1, q)
        {
            int x = qr, y = qr, ans = 0;
            int l = Wqma(x), r = Wqmi(x);
            if(1ll * ((l ^ x) - y) * ((r ^ x) - y) > 0){puts("-1"); continue;}
            l = mp[l], r = mp[r];
            if(l > r) swap(l, r);
            while(r - l > 1)
            {
                int mid = (l + r) >> 1;
                if(1ll * ((a[l] ^ x) - y) * ((a[mid] ^ x) - y) <= 0) r = mid;
                else l = mid;
            }
            if(l > r) swap(l, r);
            printf("%d\n", l);
        }
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

D. 编辑

又是神仙 dp。

\(f_{i,j}\) 表示最大的 \(x\) 使得 \(S_{[1,x]}\)\(T_{[1,x+j]}\) 的编辑距离不大于 \(i\)。原因一个是 \(k\) 很小,其次是如果 \(S_{[1,x]}\) 能在 \(i\) 次操作内成为 \(T_{[1,x+j]}\),那么 \(\forall x_0\in [1,x]\) 都满足。且合法的转移只会用到长度差不大于 \(k\) 的串,因此状态数是 \(k^2\) 级别的。

考虑如何转移,框架是枚举 \(T\) 的后缀。我们所取的串一定是一段操作与不操作交替出现的串,那么转移实质上是去找不操作的一段,即相同的一段,直接二分 + hash 求 LCP 即可。然后枚举三种操作取 max 转移。

统计答案时对于每一个 \(j\) 枚举到第一个值不小于 \(n\)\(i\) 计入答案即可。

复杂度大概是 \(\mathcal{O(nk^2\log n)}\) 的。

题解有误,不建议完全食用

image

然后坑除了边界外居然是 hash 一定得用小常数的自然溢出,取模直接在 accoder 上 TLE 飞了不知道为啥。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define lx ll
inline lx qr()
{
    char ch = getchar(); lx x = 0, f = 1;
    for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
    return x * f;
}
#undef lx
#define qr qr()
#define pii pair<int, int>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 5e4 + 5;
const int mod = 998244353;
const int base = 233;
int k, n, m;
string s, t;
ull has[N], hat[N], bas[N];
int f[35][70], ans[35];
namespace Wisadel
{
    ull Wgs(int l, int r){return has[r] - has[l - 1] * bas[r - l + 1];}
    ull Wgt(int l, int r){return hat[r] - hat[l - 1] * bas[r - l + 1];}
    int Wlcp(int x, int y)
    {
        int l = 0, r = min(n - x + 1, m - y + 1);
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(Wgs(x, x + mid - 1) == Wgt(y, y + mid - 1)) l = mid + 1;
            else r = mid - 1;
        }
        return l - 1;
    }
    short main()
    {
        freopen("edit.in", "r", stdin), freopen("edit.out", "w", stdout);
        k = qr;
        cin >> s >> t;
        n = s.size(), m = t.size();
        s = " " + s;
        t = " " + t;
        bas[0] = 1;
        fo(i, 1, max(n, m)) bas[i] = bas[i - 1] * base;
        fo(i, 1, n) has[i] = has[i - 1] * base + (s[i] - 'a');
        fo(i, 1, m) hat[i] = hat[i - 1] * base + (t[i] - 'a');
        fo(S, 1, m)
        {
            fo(i, 0, k) fo(j, 0, 2 * k) f[i][j] = 0;
            f[0][k] = 0;
            fo(i, 0, k) fo(j, -i, i)
            {
                f[i][j + k] += Wlcp(f[i][j + k] + 1, f[i][j + k] + S + j);
                if(i != k)
                    f[i + 1][j + k - 1] = max(f[i + 1][j + k - 1], min(f[i][j + k] + 1, n)),
                    f[i + 1][j + k] = max(f[i + 1][j + k], min(f[i][j + k] + 1, n)),
                    f[i + 1][j + k + 1] = max(f[i + 1][j + k + 1], f[i][j + k]);
            }
            fo(j, max(-k, 1 - n), min(k, m - S - n + 1))
            {
                fo(i, 0, k) if(f[i][j + k] >= n){ans[i]++; break;}
            }
        }
        fo(i, 0, k) printf("%d\n", ans[i]);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}
// 佳墙坂诶迦币等渔塞

你说得对但是场切一道题任务失败。

上来看 T1 发现了出题人的友善提示然后打了 \(\mathcal{O(n^2\log n)}\) 的做法。卡了很久还是在 1.2s 徘徊,然后发现题面说的时限是 2s 直接去找喵喵改了,然后后来不知道为什么又改回去了,然后后来不知道为什么又加了个点,然后就 90pts 了。

T2 依旧不会、

T3 想了会觉得线段树不可行然后意外发现解有很大概率是很靠前的数,然后 \(\mathcal{O(n^2)}\)\(10^5\) 拿 60pts。

T4 想到了最长公共子序列的错解,意外有 60pts,结果下午发现前面点答案全是 0 所以什么乱搞都过。

感觉还能更好,这次靠前只是因为暴力都打上了,其实没到大众分因为 T1 没签。

希望能一直想这样进步或者起码不退步到 NOIP,加油。


完结撒花~

image

posted @ 2024-11-07 21:09  DrRatio  阅读(71)  评论(12编辑  收藏  举报