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

Rank

还行

image

A. 传送 (teleport)

签。

单源最短路,先想 Dijkstra。发现这道题还有个不同寻常的移动方式,可以以 \(min\left(|x_i-x_j|,|y_i-y_j|\right)\) 的代价从 \(i\) 移动到 \(j\)。暴力连边是 \(\mathcal{O(n^2)}\) 的,时间空间都过不去。

被叫去整内务在楼梯上想到,一个点不应该来回走,于是想到若有 \(x_i<x_j<x_k\) (假设纵坐标不优),那么我们只用连 \(i\rightarrow j\)\(j\rightarrow k\) 这两条边即可。因此想到分别按横坐标和纵坐标给点排序,然后连接相邻的两点,再去跑 dij 即可。坐标共 \(2\times \left(n-1\right)\) 条边,由于是无向图,我们边的内存应该开 \(n\) 的 6 倍。时间复杂度仍是 \(\mathcal{O(n\log n)}\) 的。喜提最优解。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 M_P(a, b) make_pair(a, b)
#define fi first
#define se second
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 2e5 + 5;
int n, m;
int hh[N], to[N << 3], ne[N << 3], w[N << 3], cnt;
ll dis[N];
bool yz[N];
struct rmm
{
	ll dis; int u;
	bool operator < (const rmm &a) const {return a.dis < dis;}
};
struct nd
{
    int id, x, y;
} d[N], dd[N];
namespace Wisadel
{
    void Wadd(int u, int v, int val)
    {
        to[++cnt] = v;
        w[cnt] = val;
        ne[cnt] = hh[u];
        hh[u] = cnt;
    }
    void Wdij(int x)
    {
		priority_queue<rmm> q;
        memset(dis, 0x3f, sizeof dis);
		dis[x] = 0;
		q.push({0, x});
		while(q.size())
		{
			int u = q.top().u; q.pop();
			if(yz[u]) continue;
			yz[u] = 1;
            for(int i = hh[u]; i != -1; i = ne[i])
            {
                int v = to[i];
                if(dis[v] > dis[u] + w[i])
                {
                    dis[v] = dis[u] + w[i];
                    q.push({dis[v], v});
                }
            }
		}
    }
    short main()
    {
        freopen("teleport.in", "r", stdin) , freopen("teleport.out", "w", stdout);
        // freopen(".err", "w", stderr);
        n = qr, m = qr;
        memset(hh, -1, sizeof hh);
        fo(i, 1, n) dd[i].x = d[i].x = qr, dd[i].y = d[i].y = qr, d[i].id = dd[i].id = i;
        sort(d + 1, d + 1 + n, [](nd a, nd b){return a.x < b.x;});
        sort(dd + 1, dd + 1 + n, [](nd a, nd b){return a.y < b.y;});
        fo(i, 1, n - 1)
        {
            int c1 = d[i + 1].x - d[i].x, c2 = dd[i + 1].y - dd[i].y;
            Wadd(d[i].id, d[i + 1].id, c1), Wadd(d[i + 1].id, d[i].id, c1);
            Wadd(dd[i].id, dd[i + 1].id, c2), Wadd(dd[i + 1].id, dd[i].id, c2);
        }
        fo(i, 1, m)
        {
            int a = qr, b = qr, c = qr;
            Wadd(a, b, c), Wadd(b, a, c);
        }
        Wdij(1);
        fo(i, 2, n) printf("%lld ", dis[i]);puts("");
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

B. 排列 (permutation)

组合数学水题,状压 dp 也能做。

赛时发现不会插板法,于是选择分讨打表,但是到 \(\frac{n}{k}\ge 8\) 因为没有暴力帮忙核对导致漏情况了,血亏。调出来复杂度就是 \(\mathcal{O(n)}\) 的预处理,\(\mathcal{O(1)}\) 出答案。

发现只有 \(\lfloor{}\frac{n}{k}\rfloor{}\) 个数是有用的,其余的数无论如何放均不会产生 \(k\) 的 gcd。不过特殊的是,当 \(\frac{n}{k}\ge 4\) 时,总有一些数放在一起会产生 \(\gt k\) 的 gcd。因此我们考虑全排列枚举这 \(\lfloor{}\frac{n}{k}\rfloor{}\) 个数,记录它们间的 gcd 为 \(k\) 的个数,然后用插板法求这 \(\lfloor{}\frac{n}{k}\rfloor{}\) 个数的合法方案,最后乘上 \(n-\lfloor{}\frac{n}{k}\rfloor{}\) 的排列即为最终答案,复杂度大概是 \(\mathcal{O(\lfloor{}\frac{n}{k}\rfloor{}!\times \lfloor{}\frac{n}{k}\rfloor{}+n)}\) 的。

由于放不下赛时的做法,遂在正解中用了数据点分治。

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 M_P(a, b) make_pair(a, b)
#define fi first
#define se second
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 3e4 + 5, M = 3000;
const int mod = 998244353;
int n, k;
int a[N];
ll jc[N], ny[N];
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;
    }
    ll Wc(int n, int m)
    {
        return jc[n] * ny[m] % mod * ny[n - m] % mod;
    }
    short main()
    {
        freopen("permutation.in", "r", stdin) , freopen("permutation.out", "w", stdout);
        // freopen(".err", "w", stderr);
        n = qr, k = qr;
        jc[0] = ny[0] = 1;
        fo(i, 1, n) jc[i] = jc[i - 1] * i % mod;
        ny[n] = Wqp(jc[n], mod - 2);
        fu(i, n - 1, 1) ny[i] = ny[i + 1] * (i + 1) % mod;
        ll zc = n / k;
        ll ans = 1;
        if(zc <= 3) ans = jc[zc] * jc[n - zc] % mod * Wc(n - zc + 1, zc) % mod;
        else if(zc <= 5)
        {
            ans = jc[zc] * jc[n - zc] % mod * Wc(n - zc + 1, zc) % mod;
            ans = (ans + jc[zc - 1] * jc[n - zc] % mod * 2 * Wc(n - zc + 1, zc - 1) % mod) % mod;
        }
        else if(zc <= 7)
        {
            ans = jc[zc] * jc[n - zc] % mod * Wc(n - zc + 1, zc) % mod;
            ans = (ans + jc[zc - 1] * jc[n - zc] % mod * 2 % mod * Wc(n - zc + 1, zc - 1) % mod * 4 % mod) % mod;

            ans = (ans + jc[zc - 2] * jc[n - zc] % mod * 6 % mod * Wc(n - zc + 1, zc - 2) % mod) % mod;
            ans = (ans + jc[zc - 2] * jc[n - zc] % mod * 4 % mod * Wc(n - zc + 1, zc - 2) % mod) % mod;
            ans = (ans + jc[zc - 2] * jc[n - zc] % mod * 2 % mod * Wc(n - zc + 1, zc - 2) % mod * 2 % mod) % mod;

            ans = (ans + jc[zc - 3] * jc[n - zc] % mod * 4 % mod * Wc(n - zc + 1, zc - 3) % mod) % mod;
        }
        else
        {
            ans = 0;
            fo(i, 1, zc) a[i] = i;
            do
            {
                int cnt = 0;
                fo(i, 1, zc - 1) if(__gcd(a[i], a[i + 1]) == 1) cnt++;
                ans = (ans + Wc(n - cnt, zc)) % mod;
            } while(next_permutation(a + 1, a + 1 + zc));
            ans = ans * jc[n - zc] % mod;
        }
        printf("%lld\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

C. 战场模拟器 (simulator)

没学过势能线段树,不知道这样复杂度是正确的,遂没敢打,只打了 Subtask2 的 23pts。

发现这道题主要是死人不复活和护盾比较 ex,因此我们针对这两点直接暴力做就好。如果你打了 Subtask2,那么应该知道为什么要记录区间最小生命值及其数量,没打也能懂:因为每次扣血操作我们是暴力进行的,只有求濒死人数才用到最小值且只用到最小值。然后记录区间死亡人数,以及一个 lazy tag。我们发现,当区间没有盾并且打不死最小生命的英雄时我们没必要递归到单点做,因此再记录一个区间盾数量。

除了扣血,其它操作都是基本的线段树操作。在求濒死人数时还可以根据当前最小值是否 \(\gt 0\) 来节省递归的次数。

问题主要在证明这个做法的时间复杂度正确性,搜索发现并没有太好的讲解。于是只能感性理解:每个点的操作都是有上限的,在达到这个上限后便不用再进行单点修改,这样就保证了均摊复杂度。求复杂度好像用到了势能函数,不会,只给出结果:\(\mathcal{O(n\log n + 4n\log (势能))}\)。本题的复杂度是 \(\mathcal{O(n\log n+q\log n)}\) 的。

来自丁真可能更好的复杂度分析:
image

点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 M_P(a, b) make_pair(a, b)
#define fi first
#define se second
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 2e5 + 5, M = 3000;
const int mod = 998244353;
int n, m;
int a[N];
int a1[N << 2], dun[N << 2], num[N << 2];
ll s[N << 2], lazy[N << 2];
namespace Wisadel
{
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    #define mid ((l + r) >> 1)
    void Wpushup(int rt)
    {
        a1[rt] = a1[ls] + a1[rs];
        dun[rt] = dun[ls] + dun[rs];
        s[rt] = min(s[ls], s[rs]);
        num[rt] = 0;
        if(s[ls] == s[rt]) num[rt] += num[ls];
        if(s[rs] == s[rt]) num[rt] += num[rs];
    }
    void Wpushdown(int rt)
    {
        s[ls] += lazy[rt], s[rs] += lazy[rt];
        lazy[ls] += lazy[rt], lazy[rs] += lazy[rt];
        lazy[rt] = 0;
    }
    void Wbuild(int rt, int l, int r)
    {
        if(l == r)
        {
            s[rt] = a[l];
            num[rt] = 1;
            return ;
        }
        Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
        Wpushup(rt);
    }
    void Watk(int rt, int l, int r, int x, int y, int k)
    {
        if(x <= l && r <= y && s[rt] >= k && !dun[rt])
        {
            s[rt] -= k, lazy[rt] -= k;
            return ;
        }
        if(l == r)
        {
            if(dun[rt]) dun[rt]--;
            else a1[rt] = 1, num[rt] = 0, s[rt] = 4e18;
            return ;
        }
        if(lazy[rt]) Wpushdown(rt);
        if(x <= mid) Watk(ls, l, mid, x, y, k);
        if(y > mid) Watk(rs, mid + 1, r, x, y, k);
        Wpushup(rt);
    }
    void Wrec(int rt, int l, int r, int x, int y, int k)
    {
        if(x <= l && r <= y)
        {
            s[rt] += k, lazy[rt] += k;
            return ;
        }
        if(lazy[rt]) Wpushdown(rt);
        if(x <= mid) Wrec(ls, l, mid, x, y, k);
        if(y > mid) Wrec(rs, mid + 1, r, x, y, k);
        Wpushup(rt);
    }
    void Wdun(int rt, int l, int r, int x)
    {
        if(l == r)
        {
            dun[rt]++;
            return ;
        }
        if(lazy[rt]) Wpushdown(rt);
        if(x <= mid) Wdun(ls, l, mid, x);
        else Wdun(rs, mid + 1, r, x);
        Wpushup(rt);
    }
    int Wq1(int rt, int l, int r, int x, int y)
    {
        if(x <= l && r <= y) return a1[rt];
        if(lazy[rt]) Wpushdown(rt);
        int res = 0;
        if(x <= mid) res += Wq1(ls, l, mid, x, y);
        if(y > mid) res += Wq1(rs, mid + 1, r, x, y);
        return res;
    }
    int Wq2(int rt, int l, int r, int x, int y)
    {
        if(s[rt]) return 0;
        if(x <= l && r <= y) return num[rt];
        if(lazy[rt]) Wpushdown(rt);
        int res = 0;
        if(x <= mid) res += Wq2(ls, l, mid ,x, y);
        if(y > mid) res += Wq2(rs, mid + 1, r, x, y);
        return res;
    }
    short main()
    {
        freopen("simulator.in", "r", stdin) , freopen("simulator.out", "w", stdout);
        // freopen(".err", "w", stderr);
        n = qr;
        fo(i, 1, n) a[i] = qr;
        Wbuild(1, 1, n);
        m = qr;
        fo(i, 1, m)
        {
            int op = qr, l = qr, r, k;
            if(op == 1) r = qr, k = qr, Watk(1, 1, n, l, r, k);
            else if(op == 2) r = qr, k = qr, Wrec(1, 1, n, l, r, k);
            else if(op == 3) Wdun(1, 1, n, l);
            else if(op == 4) r = qr, printf("%d\n", Wq1(1, 1, n, l, r));
            else r = qr, printf("%d\n", Wq2(1, 1, n, l, r));
        }
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

D. 点亮 (light)

计数好题,咕咕咕。

image

代码十分好写。

在回了趟宿舍的干扰下打成这样感觉还行,甚至对 T1 起了正向帮助,那么______🤔🤔🤔

T2 属实是钻死胡同里去了,分讨了 2h+,策略失误,在纠正了数据点的问题后拿到 50pts,但没时间打 T3 了,想到的暴力做法就是正解却没打,血亏。

狂补组合数学。


完结撒花~

复活了。

image

posted @ 2024-10-17 18:14  DrRatio  阅读(47)  评论(6编辑  收藏  举报