『模拟赛』CSP-S模拟10

Rank

没学线性基输麻了,

image

A. 欧几里得的噩梦

线性基,输麻了。

学完了,发现这道题只需要线性基的思想就够了。

前置知识:在若干元空间内的若干条向量,可以这些向量为基底表示出该空间的任意一条向量,并且任意不同子集异或和不同。

这道题,其实就是按给定的顺序插入若干个基,插入的条件是已有的基底无法表示出这个基。考虑如何实现。我们需要判断一条基与已选定的基是否有异或值为 0 的情况,对于只有一个 1 的基来说,如果已有一个相同的,那么直接跳过;否则看是否有该位为 1 的有两个 1 的向量,那么异或值显然是另一个 1,不断重复操作,看最终结果有没有与之相同的基。发现重复操作的过程基是确定的,也就是说,给定一个 1 的位置,它终止的位置是确定的。

我们可以用并查集维护,一个 1 的位置所属连通块为它最终能到达的位置,这样就很容易将判断转化为它所属连通块有没有被选过。两个 1 的基同理,首先判断两个 1 是否在同一连通块内,否则看高位 1 是否被选过,若没有则显然可以插入,并将高位的连通块合并至低位,否则再判断低位是否被选过。

考虑这样做的实质,其实就是将其它与当前基有关的基不断异或求值,选过即表示可以用已选的基表示出当前基,那么显然不需插入。并且这样做是按照字典序升序操作的,可以确保答案是最优的。

点击查看代码
#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 fi first
#define se second
const int Ratio = 0;
const int N = 5e5 + 5;
const int mod = 1e9 + 7;
int n, m;
int fx[N], ans[N], tot;
bool yz[N];
namespace Wisadel
{
    int Wfind(int x)
    {
        return x == fx[x] ? x : fx[x] = Wfind(fx[x]);
    }
    int 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("Euclid.in", "r", stdin) , freopen("Euclid.out", "w", stdout);
        n = qr, m = qr;
        fo(i, 1, m) fx[i] = i;
        fo(i, 1, n)
        {
            int op = qr, x, y;
            if(op == 1)
            {
                x = qr;
                int _ = Wfind(x);
                if(!yz[_])
                {
                    yz[_] = 1;
                    ans[++tot] = i;
                }
            }
            else
            {
                x = qr, y = qr;
                int _ = Wfind(x), __ = Wfind(y);
                if(_ != __)
                {
                    if(_ < __) swap(_, __);
                    if(yz[_] && !yz[__])
                    {
                        yz[__] = 1;
                        ans[++tot] = i;
                    }
                    else if(!yz[_])
                    {
                        yz[_] = 1;
                        fx[_] = __;
                        ans[++tot] = i;
                    }
                }
            }
        }
        printf("%d %d\n", Wqp(2, tot), tot);
        fo(i, 1, tot) printf("%d ", ans[i]);puts("");
        return Ratio;
    }
}
int main(){return Wisadel::main();}

B. 清扫

思维题,差点签了。(感觉其实不是很难啊,没有紫的水平)

一个叶子结点和另一个叶子结点的最短路径一定经过它的父节点。根据这一性质可以让整棵树的合法性拆分成每个节点的合法性。

考虑如何判断每个节点的合法性。设其子节点的石头数之和为 \(sum\),最多的一堆数量为 \(maxn\),该节点数量为 \(c\),可知 \(c\) 的下界为 \(max\left(maxn,\lceil{\frac{sum}{2}}\rceil\right)\),上界为 \(sum\)。考虑递归时要将 \(c\) 赋值为所需要选择的石子数,这个值其实就是多余的石子数,即 \(c-(sum-c)\),最后要判断根节点的值是否为 0 才对。

算法有个细节在于根节点不能为叶子,所以对于没有叶子的情况(\(n=2\))特判一下就好。复杂度 \(\mathcal{O(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 fi first
#define se second
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n, rot;
int rd[N];
ll a[N];
int hh[N], to[N << 1], ne[N << 1], cnt;
namespace Wisadel
{
    void Wadd(int u, int v)
    {
        to[++cnt] = v;
        ne[cnt] = hh[u];
        hh[u] = cnt;
    }
    void Wdfs(int u, int fa)
    {
        ll sum = 0, maxn = -1e9; int num = 0;
        for(int i = hh[u]; i != -1; i = ne[i])
        {
            int v = to[i];
            if(v == fa) continue;
            Wdfs(v, u);
            maxn = max(maxn, a[v]), sum += a[v], num++;
        }
        if(!num) return;
        ll ned = max(maxn, sum / 2 + (sum % 2));
        if(a[u] < ned || a[u] > sum)
        {
            printf("NO\n");
            exit(0);
        }
        a[u] = 2 * a[u] - sum;
    }
    short main()
    {
        freopen("tree.in", "r", stdin) , freopen("tree.out", "w", stdout);
        n = qr;
        memset(hh, -1, sizeof hh);
        fo(i, 1, n) a[i] = qr;
        fo(i, 1, n - 1)
        {
            int a = qr, b = qr;
            rd[a]++, rd[b]++;
            Wadd(a, b), Wadd(b, a);
        }
        if(n == 2)
        {
            if(a[1] != a[2]) printf("NO\n");
            else printf("YES\n");
            return Ratio;
        }
        fo(i, 1, n) if(rd[i] >1){rot = i; break;}
        Wdfs(rot, 0);
        if(a[rot] == 0) printf("YES\n");
        else printf("NO\n");
        return Ratio;
    }
}
int main(){return Wisadel::main();}

C. 购物

签,为什么放 T3。

每个价格 \(a\) 所能对应的 \(k\) 范围为 \(\left[\lceil{\frac{a}{2}}\rceil,a\right]\),考虑向集合中加入一个数 \(b(b\gt a)\),那么所新增的 \(k\) 范围是 \(\left[\lceil{\frac{b}{2}}\rceil,a+b\right]\),只需要考虑与原来是否有交集即可,若有则贡献为 \(b\),否则贡献为 \(a+b-\lceil{\frac{b}{2}}\rceil\)。无序的数组进行添加操作会很麻烦,所以要先排序,只用维护到当前的 \(k\) 区间最右边界,然后每次算一下左边界即可。瓶颈复杂度是排序的 \(\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 fi first
#define se second
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n;
ll a[N], ans;
namespace Wisadel
{
    short main()
    {
        freopen("buy.in", "r", stdin) , freopen("buy.out", "w", stdout);
        n = qr;
        fo(i, 1, n) a[i] = qr;
        sort(a + 1, a + 1 + n);
        ll l = 0, r = 0;
        fo(i, 1, n)
        {
            l = a[i] / 2 + (a[i] % 2);
            if(l > r) ans += r - l + 1 + a[i];
            else ans += a[i];
            r += a[i];
        }
        printf("%lld\n", ans);
        return Ratio;
    }
}
int main(){return Wisadel::main();}

D. ants

回滚莫队板子题,签,为什么放 T4。

一眼数据结构,再一眼莫队,再再一眼回滚莫队,然后打就完了,基本是纯板子,不会的来这学

维护每个蚂蚁编号为左/右边界时的最大长度,发现不好进行删除操作,于是回滚。增加时赋值 \(llen_{a_i}\leftarrow llen_{a_i-1}+1\)\(rlen_{a_i}\leftarrow rlen_{a_i+1}+1\),此时最大值 \(maxn = max\left(llen_{a_i}+rlen_{a_i}-1\right)\),然后更新该块的左右边界的最大长度即可。开栈存回退信息即可。复杂度 \(\mathcal{O(n\sqrt{m})}\)

点击查看代码
#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 fi first
#define se second
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n, m, res = 0, zc = 0;
int a[N], bl[N], ed[N], ans[N], fx[N];
int llen[N], rlen[N], lcnt, rcnt;
pii lzc[N], rzc[N];
bool yz[N];
struct rmm
{
    int l, r, id;
} q[N];
bool cmp(rmm a, rmm b)
{
    if(bl[a.l] == bl[b.l]) return a.r < b.r;
    return bl[a.l] < bl[b.l];
}
namespace Wisadel
{
    int Wfind(int x)
    {
        if(x == fx[x]) return x;
        return fx[x] = Wfind(fx[x]);
    }
    void Wadd(int x, bool fla)
    {
        llen[a[x]] = llen[a[x] - 1] + 1;
        rlen[a[x]] = rlen[a[x] + 1] + 1;
        int zc = llen[a[x]] + rlen[a[x]] - 1;
        res = max(res, zc);
        if(fla)
        {
            lzc[++lcnt] = {a[x] + rlen[a[x]] - 1, llen[a[x] + rlen[a[x]] - 1]};
            rzc[++rcnt] = {a[x] - llen[a[x]] + 1, rlen[a[x] - llen[a[x]] + 1]};
        }
        llen[a[x] + rlen[a[x]] - 1] = rlen[a[x] - llen[a[x]] + 1] = zc;
    }
    void Wback(int l, int r)
    {
        res = zc;
        fu(i, lcnt, 1) llen[lzc[i].fi] = lzc[i].se;
        fu(i, rcnt, 1) rlen[rzc[i].fi] = rzc[i].se;
        fo(i, l, r) llen[a[i]] = rlen[a[i]] = 0;
    }
    short main()
    {
        freopen("ants.in", "r", stdin) , freopen("ants.out", "w", stdout);
        n = qr, m = qr;
        int sq = sqrt(n);
        fo(i, 1, sq) ed[i] = (n / sq) * i;
        ed[sq] = n;
        fo(i, 1, sq) fo(j, ed[i - 1] + 1, ed[i]) bl[j] = i;
        fo(i, 1, n) a[i] = qr, fx[i] = i;
        fo(i, 1, m) q[i].l = qr, q[i].r = qr, q[i].id = i;
        sort(q + 1, q + 1 + m, cmp);
        int l, r = 0;
        fo(i, 1, m)
        {
            if(bl[q[i].l] != bl[q[i - 1].l])
            {
                res = 0;
                fo(i, 1, n) llen[a[i]] = rlen[a[i]] = 0;
                r = ed[bl[q[i].l]];
            }
            while(r < q[i].r) Wadd(++r, 0);
            zc = res;
            lcnt = rcnt = 0;
            l = min(q[i].r, ed[bl[q[i].l]]);
            fo(j, q[i].l, l) Wadd(j, 1);
            ans[q[i].id] = res;
            Wback(q[i].l, l);
        }
        fo(i, 1, m) printf("%d\n", ans[i]);
        return Ratio;
    }
}
int main(){return Wisadel::main();}

开题顺序立大功。由 T1 给出的:

image

所以先去做 T3 了(?),整体顺序 \(C\rightarrow D\rightarrow B \rightarrow A\)

离 AK 最近的一场,不过现在也该学线性基了,而且 T2 漏 Corner Case 挺不应该的,还有进步空间吧。

剩下的改完 T1 再说。

幸好回滚莫队思路没忘,T3 的思路也很清晰。也许切了两道题后再看题和直接看题相比确实有增益 buff。

稳住。


完结撒花~

我超,市斯卡蒂🤤🤤🤤

image

posted @ 2024-10-08 17:00  DrRatio  阅读(63)  评论(0编辑  收藏  举报