『模拟赛』NOIP2024加赛7

Rank

打满了(O(∩_∩)O~~

image

A. 镜的绮想 (mirror)

签。最近的签真是越来越签了。

做法直截了当,枚举每个点对计算对称点,然后开个桶数组计数即可。

优化是对于每个横坐标开 vector 存,省掉很多无用的枚举;将负坐标加上 106 转成正数,将浮点对称点乘 2 转成整形,只用开 4×106 的数组。

点击查看代码
#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 long double ld;
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 ppp pair<pii, pii>
#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 = 2e6 + 5;
const int mod = 1e9 + 7;
int n, m, ans;
vector<int> shi[N], xu[N];
int maxx[N << 1];
namespace Wisadel
{
    short main()
    {
        freopen("mirror.in", "r", stdin), freopen("mirror.out", "w", stdout);
        n = qr, m = qr;
        fo(i, 1, n)
        {
            int x = qr + 1000000, y = qr + 1000000;
            shi[x].P_B(y);
        }
        fo(i, 1, m)
        {
            int x = qr + 1000000, y = qr + 1000000;
            xu[x].P_B(y);
        }
        fo(i, 0, 2000000)
        {
            for(auto u : shi[i]) for(auto v : xu[i])
            {
                double zc = 1.0 * abs(u - v) / 2 + min(u, v);
                int cz = zc * 2;
                maxx[cz]++, ans = max(maxx[cz], ans);
            }
        }
        printf("%d\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}
// All talk and never answer

B. 万物有灵 (animism)

比较智慧的题。瞪了两个半小时。

比较显然的是,某一层的点数一定不小于上一层的。那么答案很显然,从最后一层开始,隔一层选一层,问题就变成了一个算数题。

考虑每一层的点数是多少。若 iktoti=j=0i1aj,若 k<i2×ktoti=(j=0k1aj)(j=0ik1aj)。容易发现某一层的点数等于一个前缀积的形式。

si=j=0i1ai(1ik),那么考虑当 k 为偶数时,答案易得出:

ans=s1+s3++sk1+sks1+sks3+=s1(1+sk+sk2++sknk)+s2()+=(1+sk++sknk)(s1+s3++sk1)+res

其中 resn 除以 k 的余数部分,可以在求出 sknk 后单独算。

那么问题就来到了如何快速求出系数部分 (1+sk++sknk)。这是一个等比数列求和问题,公式中用到了除法,但这道题完全没有给出存在逆元的信息,所以考虑用类似快速幂的做法求出。

赛时切关键在发现了一个式子:(1+s)(1+s2)(1+s4)(1+sk)=i=02k1si,其中 k 为 2 的正整数次幂。发现这个结果跟我们要求的很像,倍增的形式正好和快速幂相符,于是把它按进快速幂中就做完了!

long long x = s[k-1], x2 = all, y = n / k, resmul = 1, res = 0;
// all - 单项式,resmul - 等比数列最高项结果,res - 等比数列与后面的单项式的乘积
while(y)
{
	if(y & 1) res = (res + resmul * x2 % mod) % mod, resmul = resmul * x % mod;
	x2 = x2 * (1 + x) % mod;
	x = x * x % mod;
	y >>= 1;
}

那么直接将求出的 res 计入答案即可。剩下的就是余数部分,直接 O(k) 退过去就完了。还有细节是要特殊处理根那一层。

总体复杂度 O(k+lognk)

点击查看代码
#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 long double ld;
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 ppp pair<pii, pii>
#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;
ll n, k, mod, ans, all;
ll a[N], s[N];
namespace Wisadel
{
    void Wdo()
    {
        ll res = 1, x = s[k - 1], y = n / k, zc = all;
        while(y)
        {
            if(y & 1) ans = (ans + res * zc % mod) % mod, res = res * x % mod;//, cout<<ans<<' '<<res<<endl;
            zc = zc * (1 + x) % mod;
            x = x * x % mod;
            y >>= 1;
        }
        fo(i, 0, (n % k) - 1)
        {
            res = res * a[i] % mod;
            if((n - i) & 1) ans = (ans + res) % mod;
        }
    }
    short main()
    {
        freopen("animism.in", "r", stdin), freopen("animism.out", "w", stdout);
        n = qr, k = qr, mod = qr;
        fo(i, 0, k - 1) a[i] = qr, s[i] = (i == 0 ? 1 : s[i - 1]) * a[i] % mod;
        if(k & 1)
        {
            fo(i, k, 2 * k - 1) a[i] = a[i - k], s[i] = s[i - 1] * a[i] % mod;
            k = 2 * k;
        }
        ans = (n & 1) ? 0 : 1;
        fo(i, 0, k - 1) if((n - i) & 1) all = (all + s[i]) % mod;
        Wdo();
        printf("%lld\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}
// All talk and never answer

C. 白石溪 (creek)

诈骗题。

O(n2) dp 做法是好想的,和 CSP T3 一个水平。但是尝试优化你会发现根本优化不了,转移无论如何需要 O(n) 转移不同数量的红石头的答案。

所以考虑贪心(?)。将所有点的贡献拆分,以红点为例,贡献为 ai+(ipri)d,其中 pri 为区间 [1,i] 中同色石头数。可以将全局贡献拆成 ai+idpri,前一部分是不变的,后一部分只和红石头总数有关。

那么考虑先都涂成蓝色,然后逐个涂红考虑,发现单独考虑一个点,其增加的贡献为 aibi+(i1)d+(ni)c。那么直接排序,然后逐个考虑是否加入即可。复杂度是排序的 O(nlogn)

注意细节要减去加入多个红点的重复贡献。

点击查看代码
#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 long double ld;
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 pll pair<ll, ll>
#define pii pair<int, int>
#define ppp pair<pii, pii>
#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;
const int mod = 1e9 + 7;
int n, c, d;
pll a[N];
ll v[N], sum, ans;
namespace Wisadel
{
    short main()
    {
        freopen("creek.in", "r", stdin), freopen("creek.out", "w", stdout);
        n = qr, c = qr, d = qr;
        fo(i, 1, n)
        {
            a[i].fi = qr, a[i].se = qr;
            sum += a[i].se;
            v[i] = a[i].fi - a[i].se + 1ll * (i - 1) * d + 1ll * (n - i) * c;
        }
        sort(v + 1, v + 1 + n, [](ll &A, ll &B){return A > B;});
        fo(i, 1, n)
        {
            sum += v[i];
            ans = max(ans, sum - 1ll * i * (i - 1) / 2 * (c + d));
        }
        printf("%lld\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}
// All talk and never answer

D. 上山岗 (uphill)

比较好想的是 O(n2logn) 的暴力二分做法,可以拿到 60pts。将山和人都升序排序,先扫描线求出最多登顶次数,然后二分求出每座山对应的能力值最大的能保持这个登顶次数的人。

考虑正解。我们维护两棵线段树,一棵只标记已选中的山,另一棵额外标记在找最多登顶次数中预定的山。

我们只将人排序,因为人的顺序与答案显然无关。线段树维护区间山高最小值。我们首先依然要求出最多登顶次数,在每个人能力范围内选最靠右的山峰作为预定,线段树上二分直接做,并在一棵线段树中标记出来。

此时我们倒序枚举每个人,若枚举到的人没有产生贡献(即第一次操作中没有预定的山),那么直接给他匹配当前未被选中的最靠左的山。考虑这样做的合法性。首先字典序的要求显然满足,其次考虑若匹配的山没有被预定过,那么显然不会有影响;若被预定过,则当前人会抢走原来人的贡献,总答案不变,直接删掉原来人的贡献即可。故这样操作是最优的。

若枚举到的人有贡献,则需要在同时标记了预定的山的那棵线段树中二分出一个最靠左的合法的山。考虑这样做的合法性。我们是按能力值从大到小枚举的人,而且在第一步操作中预定的山都是靠右的,所以此时最优的决策一定不在比他能力值小的人预定的山中,而此时他又一定要产生贡献,因此唯一合法且最优的操作方法就是在标记了已选中和预定的那棵线段树中二分最左的合法山。

那么就做完了!我们只需要建两棵支持单点修改和二分的线段树即可,标记之类的细节容易自己想出来。时间复杂度是 O(nlogn) 的,常数很小,速度是 wang54321 的十多倍

点击查看代码
#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 long double ld;
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 pll pair<ll, ll>
#define pii pair<int, int>
#define ppp pair<pii, pii>
#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;
const int mod = 1e9 + 7;
int n;
int c[N], w[N], pre[N], cl[N], ans[N];
struct rmm
{
    int mn[N << 2];
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    #define mid ((l + r) >> 1)
    inline void Wpushup(int rt){mn[rt] = min(mn[ls], mn[rs]);}
    inline void Wbuild(int rt, int l, int r)
    {
        if(l == r) return mn[rt] = c[l], void();
        Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
        Wpushup(rt);
    }
    inline void Wupd(int rt, int l, int r, int x, int k)
    {
        if(l == r) return mn[rt] = k, void();
        if(x <= mid) Wupd(ls, l, mid, x, k);
        else Wupd(rs, mid + 1, r, x, k);
        Wpushup(rt);
    }
    inline int Wqian(int rt, int l, int r, int x)
    {
        if(l == r) return mn[rt] < x ? l : 1e9;
        if(mn[ls] < x) return Wqian(ls, l, mid, x);
        return Wqian(rs, mid + 1, r, x);
    }
    inline int Whou(int rt, int l, int r, int x)
    {
        if(l == r) return mn[rt] < x ? l : 1e9;
        if(mn[rs] < x) return Whou(rs, mid + 1, r, x);
        return Whou(ls, l, mid, x);
    }
} Tok, Ttot;
namespace Wisadel
{
    short main()
    {
        // freopen(".in", "r", stdin), freopen(".out", "w", stdout);
        freopen("uphill.in", "r", stdin), freopen("uphill.out", "w", stdout);
        n = qr;
        fo(i, 1, n) c[i] = qr;
        fo(i, 1, n) w[i] = qr;
        sort(w + 1, w + 1 + n);
        Tok.Wbuild(1, 1, n), Ttot.Wbuild(1, 1, n);
        fo(i, 1, n)
        {
            int zc = Tok.Whou(1, 1, n, w[i]);
            if(zc != 1e9)
            {
                cl[i] = zc, pre[zc] = i;
                Tok.Wupd(1, 1, n, zc, 2e9);
            }
        }
        fu(i, n, 1)
        {
            int zc = 0;
            if(!cl[i])
            {
                zc = Ttot.Wqian(1, 1, n, 2e9);
                cl[pre[zc]] = 0;
            }
            else
            {
                Tok.Wupd(1, 1, n, cl[i], c[cl[i]]);
                zc = Tok.Wqian(1, 1, n, w[i]);
            }
            ans[zc] = w[i];
            Tok.Wupd(1, 1, n, zc, 2e9), Ttot.Wupd(1, 1, n, zc, 2e9);
        }
        fo(i, 1, n) printf("%d ", ans[i]);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}
// All talk and never answer

NOIP 还在倒数。。

这几天状态都还好,也可能是题简单了些,对 T2 的恐惧也逐渐打消了。

T2 上来看到 1018 想了好久的矩阵加速,不过矩阵一直设计不出来,好在推式子推到了能用倍增解决的一步,然后插到类似快速幂的东西里就跑完了。

好像是为 agc 腾时间,所以 abc 改到今天晚上了,无论如何还是想打一打,毕竟以后想打可能都没机会了。

小剧场

Ratio:abc 真是打一场少一场了。

wkh2008:人一生说的话也是有限的,话也是说一句少一句了。

Ratio:我怎么觉得是说一句多一句🤔

wkh2008:这是悲观的人和乐观的人的区别


完结撒花~

暂时没有花 现在有了

image

posted @   Ratio_Y  阅读(116)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示