2024.11.21 NOIP 模拟赛 题解

T1 迷宫(maze)

题意

给定一个 \(n\times m\) 的迷宫,每秒可以向上下左右中一个方向走一步,或花 \(t\) 秒移动到曼哈顿距离不超过 \(k\) 的任意位置,全程不能进入 \('\#'\),求从 \('S'\) 移到 \('T'\) 的最少时间,无法到达输出 \(-1\)\(n,m\le2000,k\le 8,t\le10^9\),时限 \(2s\)

分析

显然是最短路问题,其中点数为 \(O(n^2)\),边数为 \(O(n^2k^2)\)

若使用 \(spfa\),时间复杂度平均为 \(O(km)\),无法通过(并且可能被卡到 \(O(n^4k^2)\),更加不可能通过)

若使用堆优化 \(dijkstra\),则时间复杂度 \(O(n^2k^2(\log n+\log k))\),显然无法通过(据说手写堆可以做到 \(O(n^2k^2+n^2(\log n+\log k))\),也许可以过)

发现边权只有 \(1\)\(t\)

因此建立两个队列 \(Q_1,Q_2\)\(Q_1\) 保存由边权为 \(1\) 的边扩展出的,\(Q_2\) 保存由边权为 \(t\) 的边扩展出的

显然两个队列内部是有序的

每次比较两者头部较小的,取出并扩展,将扩展出的加入队尾

时间复杂度 \(O(n^2k^2)\),实测最大点约 \(300ms\)(手写队列)

代码:

#include <bits/stdc++.h>
using namespace std;
int n, m, k, t, sx, sy, tx, ty;
char c[2010][2010];
int X1[2010 * 2010], Y1[2010 * 2010], lp1 = 1, rp1 = 1;
int X2[2010 * 2010], Y2[2010 * 2010], lp2 = 1, rp2;
long long ds[2010][2010]; bool td[2010][2010];
int main(){
    freopen("maze.in", "r", stdin);freopen("maze.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m >> k >> t; memset(ds, 0x3f, sizeof(ds));
    for (int i = 1; i <= n; ++i)for (int j = 1; j <= m; ++j)cin >> c[i][j];
    for (int i = 1; i <= n; ++i)for (int j = 1; j <= m; ++j)
        if (c[i][j] == 'S')sx = i, sy = j;else if (c[i][j] == 'T')tx = i, ty = j;
    ds[sx][sy] = 0;X1[1] = sx, Y1[1] = sy;
    while (lp1 <= rp1 || lp2 <= rp2){
        long long D1 = lp1 <= rp1? ds[X1[lp1]][Y1[lp1]] : (long long)1e18;
        long long D2 = lp2 <= rp2? ds[X2[lp2]][Y2[lp2]] : (long long)1e18; int X, Y;
        if (D1 <= D2)X = X1[lp1], Y = Y1[lp1++]; else X = X2[lp2], Y = Y2[lp2++]; if (td[X][Y])continue; td[X][Y] = 1;
        if (X == tx && Y == ty){cout << ds[X][Y] << endl;return 0;}
        if (X > 1 && c[X - 1][Y] != '#' && ds[X - 1][Y] > ds[X][Y] + 1)ds[X - 1][Y] = ds[X][Y] + 1, X1[++rp1] = X - 1, Y1[rp1] = Y;
        if (X < n && c[X + 1][Y] != '#' && ds[X + 1][Y] > ds[X][Y] + 1)ds[X + 1][Y] = ds[X][Y] + 1, X1[++rp1] = X + 1, Y1[rp1] = Y;
        if (Y > 1 && c[X][Y - 1] != '#' && ds[X][Y - 1] > ds[X][Y] + 1)ds[X][Y - 1] = ds[X][Y] + 1, X1[++rp1] = X, Y1[rp1] = Y - 1;
        if (Y < m && c[X][Y + 1] != '#' && ds[X][Y + 1] > ds[X][Y] + 1)ds[X][Y + 1] = ds[X][Y] + 1, X1[++rp1] = X, Y1[rp1] = Y + 1;
        for (int x = max(1, X - k), _x = min(n, X + k); x <= _x; ++x)
            for (int dsx = k - (x < X? X - x : x - X), y = max(1, Y - dsx), _y = min(m, Y + dsx); y <= _y; ++y)
                if (c[x][y] != '#' && ds[x][y] > ds[X][Y] + t)ds[x][y] = ds[X][Y] + t, X2[++rp2] = x, Y2[rp2] = y;
    }
    cout << "-1" << endl;
    return 0;
}
/*
4 4 3 4
S.#.
..#.
.#..
.#.T

7


8 10 4 112
##.##.####
#####..##.
#T#.######
#####.##.#
#########.
.#.#####.#
#########.
S###.#.###

224
*/

T2 玩具(toy)

题意

给定 \(p_{1\sim n}\;(p_i\in\{0,1\})\),保证前 \(b\) 个和最后 \(b\) 个为 \(1\),有 \(b\) 个指针,初始位于 \(1\sim b\),一个区间初始在 \([1,a]\),每次可以花 \(1\) 的代价令区间右移一个单位,或将一个指针向右移若干格,要求全程所有指针都在区间内,指针间相对顺序不变,指针位置互不重叠,且任意时刻每个指针的位置 \(p\) 必须为 \(1\),求令指针位于 \(n-b+1\sim n\),区间位于 \([n-a+1\sim n]\) 的最小代价,需要判无解,\(1\le b\le a\le n\le3\times10^6\)

分析

可以证明,每当最左边的指针将要超出区间时,才会将所有指针右移,且每次都移到当前区间内最右边 \(b\)\(p\)\(1\) 的位置

因此直接模拟即可

时间复杂度 \(O(n)\),可能需要优化读入

代码:

#include <bits/stdc++.h>
using namespace std;
constexpr unsigned MXSZ = 1 << 20;char buf[MXSZ], *p1, *p2;
#ifdef testing
#define gc getchar()
#else
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MXSZ, stdin), p1 == p2)? EOF : *p1++)
#endif
int read(){
    int x = 0;bool f = false;int c = gc;for (; !isdigit(c); c = gc)if (c == '-')f = true;
    for (; isdigit(c); c = gc)x = (x << 1) + (x << 3) + (c ^ 48);return f? -x : x;
}
bool st[3000010];
long long rsum[3000010], sm, ans;
int b, a, n, lp[3000010], tmp[3000010], Lp = 1, Rp, llg = 1;
int main(){
    freopen("toy.in", "r", stdin);freopen("toy.out", "w", stdout);
    b = read(), a = read(), n = read();char _c = gc; while (isspace(_c))_c = gc;
    for (int i = 1; i <= n; ++i)st[i] = _c - '0', _c = gc;
    if (a == b){if (a == n)cout << 0 << endl;else cout << "IMPOSSIBLE" << endl;return 0;}
    for (int i = 1; i <= n; lp[i] = tmp[Lp], rsum[i] = sm, ++i)
        if (st[i]){++Rp;tmp[Rp] = i;sm += i;if (Rp - Lp + 1 > b){sm -= tmp[Lp];++Lp;}}
    for (int L = 1, R = a; R <= n; ++L, ++R){
        if (llg == L || R == n)if (llg < lp[R])ans += b, llg = lp[R];
        if (R != n)++ans;if (rsum[R] < L || llg < L){cout << "IMPOSSIBLE" << endl;return 0;}
    }
    cout << ans << endl;
    return 0;
}
/*
1 3 5
11011

5


33 80 1000
11111111111111111111111111111111111010110001111110000111101011001101100011001111111110111011011100011101110001110110001011001011100111100111
01010001111011000110101101010010000110111101001001100111011011111011101000111011100001101100110001101110111011011011111111010100101111001011
11111110101111111001001101110110001000100101010000100110110100101010110101111101101110100010010110001111110111111100010100101101110110001100
00110100101011011010111001101011011010111011001010001011011111001100001111100101100101000101001000100010101010011111010011110101010101100010
01111110010010011101011010110110111001100111011101011000001001111101011011100111000101100111100111011001100010111111101101000001010111111011
11000011010101110011001010001101110011111101110110110011011100111001101101011100100110011011100110111101101110110010111011100111011011010101
00110110101100011111100100011110000011111000010101110111111000111111001101011111001001110010101111110010110111101100011111011011111111111111
11111111111111111111

2273
*/

T3 权重(weight)

题意

给定 \(n\) 点的树,每个点有点权 \(w\)。定义一条路径的权值为路径上所有点(含两端)的点权异或和,对于每个点 \(u\) 等概率选择一个 \(v\),求 \(n\) 组路径 \(u-v\) 中有权值不小于 \(t\) 的路径的概率乘以 \(n^n\) 取模,\(n\le3\times10^5,0\le w_i,t<2^{32}\),时限 \(3s\)

分析

\(ct_u=\sum_{v=1}^n[\bigoplus_{p\in path(u,v)}w_p\ge t]\),则答案为 \(n^n-\prod_{i=1}^n(n-ct_i)\),考虑求解 \(ct\)

方法 1(赛时方法)

考虑点分治

分治到 \(Md\) 时处理所有经过 \(Md\) 的路径的贡献

按照点分治的常用实现,可以将所需操作归为以下几个:

  1. 向一个可重数集 \(S\) 中插入一个数
  2. 查询 \(\sum_{u\in S}[u\oplus v\ge t]\)
  3. 清空可重数集

显然可以用一个 \(Trie\) 维护上述操作

时间复杂度 \(O(n\log n\log V)\)

代码:

#include <bits/stdc++.h>
#define M 998244353
using namespace std;
int add(int x, int y){x += y;return x >= M? x - M : x;}
void sadd(int &x, int y){x += y;if (x >= M)x -= M;}
int sub(int a, int b){a -= b;return a < 0? a + M : a;}
int mul(int a, int b){return static_cast<long long>(a) * b % M;}
void smul(int &a, int b){a = mul(a, b);}
int fpw(int a, int b){int ret = 1;for (; b; b >>= 1, smul(a, a))if (b & 1)smul(ret, a);return ret;}
int n;vector<int> ve[300010];unsigned w[300010], t;
struct trie{
    int nx[300010 * 32][2], sz[300010 * 32], tot;
    void ins(unsigned v){
        for (unsigned i = 31, nw = 0; ~i; --i)
        {int &T = nx[nw][v >> i & 1];if (!T)T = ++tot, nx[tot][0] = nx[tot][1] = sz[tot] = 0;nw = T; ++sz[nw];}
    }
    void clear(){nx[0][0] = nx[0][1] = tot = 0;}
    int qrygr(unsigned v){//cnt xor v,>=t
        int ret = 0;
        for (unsigned i = 31, nw = 0, hsd = 0; ~i; --i){
            int t = v >> i & 1;
            if ((hsd | (1u << i)) >= ::t)ret += sz[nx[nw][t ^ 1]], nw = nx[nw][t];
            else hsd |= 1u << i, nw = nx[nw][t ^ 1]; if (!nw)return ret;
        }
        return ret;
    }
} Tr;
int rs[300010], Md, Al, msz, sz[300010];
bool mkd[300010];
void FndMd(int k, int fa){
    sz[k] = 1; int Mmsz = 0;
    for (int i : ve[k])if (i ^ fa && !mkd[i])FndMd(i, k), sz[k] += sz[i], Mmsz = max(Mmsz, sz[i]);
    Mmsz = max(Mmsz, Al - sz[k]);if (Mmsz < msz)msz = Mmsz, Md = k;
}
vector<pair<int, unsigned> > Tp;
void pushds(int k, int fa, unsigned wt)
{wt ^= w[k];Tp.emplace_back(k, wt);for (int i : ve[k])if (i ^ fa && !mkd[i])pushds(i, k, wt);}
void sol(){
    if (w[Md] >= t)++rs[Md];
    for (int _ = 0; _ < 2; ++_, reverse(ve[Md].begin(), ve[Md].end())){
        for (int i : ve[Md])if (!mkd[i]){
            Tp.clear();pushds(i, Md, 0);sz[i] = Tp.size();
            for (auto i : Tp){rs[i.first] += Tr.qrygr(i.second);if (!_)if ((i.second ^ w[Md]) >= t)++rs[i.first], ++rs[Md];}
            for (auto i : Tp)Tr.ins(i.second ^ w[Md]);
        }
        Tr.clear();
    }
    mkd[Md] = 1; for (int i : ve[Md])if (!mkd[i]){msz = 1e9; Al = sz[i]; FndMd(i, 0); sol();}
}
int main(){
    freopen("weight.in", "r", stdin);freopen("weight.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    cin >> n;
    for (int i = 1, u, v; i < n; ++i){cin >> u >> v;ve[u].emplace_back(v);ve[v].emplace_back(u);}
    for (int i = 1; i <= n; ++i)cin >> w[i];
    cin >> t;msz = 1e9;Al = n; FndMd(1, 0); sol();int ans = 1;
    for (int i = 1; i <= n; ++i)smul(ans, sub(n, rs[i]));
    cout << sub(fpw(n, n), ans) << endl;
    return 0;
}
/*
4
1 2
2 3
1 4
1 3 2 0
4

0


4
1 2
2 3
1 4
1 3 2 2
3

148


5
5 3
2 1
4 2
1 3
2827309828 1072955396 267871571 670894092 2402751778
2218918831

3053
*/

方法 2(题解思路)

\(\overline{w}_u\)\(u\) 到根的路径的权值

则路径 \(u-v\) 的权值等于 \(\overline{u}\oplus\overline{v}\oplus w_{\operatorname{lca}(u,v)}\)

因此枚举 \(\operatorname{lca}\)

设当前枚举的 \(\operatorname{lca}\)\(u\),其儿子集合为 \(s_{1\sim k}\),每个子树 \(s_i\) 内点集为 \(S(s_i)\)

假设 \(s_1\) 为子树中最大的一个

扫描 \(s_{1\sim k}\),处理完一个就将 \(s_i\) 全部插入 \(S_0\),初始 \(S_0\) 继承自 \(s_1\)

设当前处理到 \(s_i\),之前所有都插入了 \(S_0\)

对于每个 \(v\in S(s_i)\),若存在一个 \(k\in S_0\),满足 \(\overline{w}_k\oplus\overline{w}_v\oplus w_u\ge t\),则会令 \(v\)\(k\) 的答案都加一

使用 \(Trie\) 维护 \(S\),则合法的 \(k\) 恰好为 \(Trie\) 内不超过 \(O(\log V)\) 个子树的所有节点

\(v\) 的贡献就是这些子树的大小之和,对 \(k\) 的贡献相当于令子树内所有点答案加一,可以使用标记维护

总时间复杂度 \(O(n\log n\log V)\)

据说使用压缩 \(Trie\)时间复杂度可以除以 \(\log\log n\)

T4 周长(perimeter)[ARC063F] すぬけ君の塗り絵 2

题意

有一个 \(w\times h\) 的矩形,其中有 \(n\) 个整点,对于每个点 \((a,b)\),需要将平面沿 \(x=a\)\(y=b\) 剖开并删去其中一部分(可能为空),求最后剩下的矩形周长最大值,\(n\le3\times10^5,w,h\le10^8\)

分析

显然答案的下界为 \(2(\max(w,h)+1)\)(一个长为 \(\max(w,h)\),宽为 \(1\) 的矩形)

因此最优解一定横跨 \(x=\frac w2\)\(y=\frac h2\),否则不比这个优

考虑横跨 \(y=\frac h2\) 的答案,另一个交换两坐标即可

插入两个点 \((0,0)\)\((w,h)\),以减少边界判断

将点按 \(x\) 坐标排序

枚举矩形 \(x\) 坐标的最大值 \(r\),维护一棵线段树,每个位置 \(l\) 存储 \(-l+u-d\),其中 \(u\)\(d\) 分别为最大最小的 \(y\) 坐标,满足 \(x\in (l,r),y\in (d,u)\) 范围内没有点

随着 \(l\) 的递增,显然 \(u\) 单调不上升,\(d\) 单调不下降

因此使用两个单调栈 \(su\)\(sd\),存储若干二元组 \((l,v)\),表示 \(l\) 到下一个二元组(不存在则为当前的 \(r\))之前的一段 \(u\)(或 \(d\))的值等于 \(v\)

每次加入一个新的点,若其在 \(y=\frac h2\) 上方,则将其插入 \(su\),并在弹栈时令线段树相应区间减去差值;在下方同理

同时跟新新加入点在线段树中的位置的值

最后用下一个点\(x\) 坐标(因为点可以在边界上,因此尽量向后取)加上线段树的全局最大值跟新答案

时间复杂度 \(O(n\log n)\),常数较大

代码

参考

比赛结果

\(45+100+30+24\)

\(T1\) 看错了数据规模致数组开小,没有判无解,且一个优化有误,错失 \(\color{red}55pts\)\(T2\) 符合预计;\(T3\) 由于命名习惯问题最后几分钟没找到问题,错失 \(\color{red}70pts\)\(T4\) 预计部分分 \(62pts\),但算法有误丢 \(\color{red}38pts\);总计比预计少 \(\Large{\color{red}163pts}\),为近期失误最多的一次

posted @ 2024-11-21 19:34  Hstry  阅读(66)  评论(0)    收藏  举报