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

Rank

打成大奋了

image

A. 选彩笔(rgb)

我是彩笔

赛时完全不会啊,打了一个 25k 的贪心结果爆栈了喜提 0pts。

最大值最小,还是二分答案。二分的答案是最大差,发现值域很小,我们在 check 时可以直接枚举每个色号的最大值,统计在所选区间范围内笔数。这样就有了 O(w3nlogw) 的暴力。

考虑三维前缀和优化,O(w3) 预处理,容斥求值,然后可以在 O(w3logw) 的复杂度下过掉了。理论算着可能会 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;
#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 P_B(x) push_back(x)
const int Ratio = 0;
const int N = 1e5 + 5;
int n, m, ans;
struct rmm{int r, g, b, id;} p[N];
int sum[260][260][260];
namespace Wisadel
{
    ll Wq(int i, int j, int k, int mid)
    {
        ll res = sum[i][j][k];
        if(i >= mid)
        {
            if(j >= mid)
            {
                if(k >= mid) res -= sum[i - mid][j - mid][k - mid];
                res += sum[i - mid][j - mid][k];
            }
            if(k >= mid) res += sum[i - mid][j][k - mid];
            res -= sum[i - mid][j][k];
        }
        if(j >= mid)
        {
            if(k >= mid) res += sum[i][j - mid][k - mid];
            res -= sum[i][j - mid][k];
        }
        if(k >= mid) res -= sum[i][j][k - mid];
        return res;
    }
    bool Wck(int tar)
    {
        fo(i, tar - 1, 255) fo(j, tar - 1, 255) fo(k, tar - 1, 255)
            if(Wq(i, j, k, tar) >= m) return 1;
        return 0;
    }
    short main()
    {
        freopen("rgb.in", "r", stdin), freopen("rgb.out", "w", stdout);
        n = qr, m = qr;
        fo(i, 1, n) p[i].r = qr, p[i].g = qr, p[i].b = qr, sum[p[i].r][p[i].g][p[i].b]++;
        fo(i, 1, 255) fo(j, 0, 255) fo(k, 0, 255) sum[i][j][k] += sum[i - 1][j][k];
        fo(i, 0, 255) fo(j, 1, 255) fo(k, 0, 255) sum[i][j][k] += sum[i][j - 1][k];
        fo(i, 0, 255) fo(j, 0, 255) fo(k, 1, 255) sum[i][j][k] += sum[i][j][k - 1];
        int l = 0, r = 255, ans = 255;
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(Wck(mid + 1)) r = mid - 1, ans = mid;
            else l = mid + 1;
        }
        printf("%d\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

B. 兵蚁排序(sort)

也很水的一个题,赛时因为 T1 想假了耽误很长时间所以没多想,推出一个假性质居然能模过所有样例,但是假,所以 30pts。

先说说赛时的假做法:首先因为发现了大的数不可能往前走,所以我是倒着做的,考虑扫到的每一个数:比 bi 大,无解,比 bi 小,则向前找到第一个 bi,然后对整个区间排序。你会发现由于是一整块直接排序,中间许多本来符合目标序列关系的数被打乱了,因此会多判许多无解的情况。

那么根据这个错误,推出一个有正确性的做法:正着扫,然后对于当前序列向前扫到第一个等于 bi 的值,然后一个一个交换判断是否可行。考虑这样为什么是对的:我们逐个移动保证了移动后序列仍然满足一些原序列的逆序关系,目标序列若也存在这种关系就不管,否则再交换一次即可打破逆序关系,因此这样动不会漏掉情况。

复杂度 O(Tn2)

点击查看代码
#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 = 1e5 + 5;
int n;
int a[N], b[N];
multiset<int> st;
vector<pii> ans;
namespace Wisadel
{
    short main()
    {
        freopen("sort.in", "r", stdin), freopen("sort.out", "w", stdout);
        int T = qr;
        while(T--)
        {
            n = qr; ans.clear();
            fo(i, 1, n) a[i] = qr;
            fo(i, 1, n) b[i] = qr;
            bool bok = 0;
            fo(i, 1, n)
            {
                int zc = 0;
                fo(j, i, n) if(b[i] == a[j]){zc = j; break;}
                fu(j, zc, i + 1)
                {
                    if(a[j] < a[j - 1])
                    {
                        ans.P_B(M_P(j - 1, j));
                        swap(a[j], a[j - 1]);
                    }
                    else{bok = 1; break;}
                }
                if(bok) break;
            }
            if(bok) puts("-1");
            else
            {
                puts("0");
                printf("%d\n", ans.size());
                for(auto i : ans) printf("%d %d\n", i.fi, i.se);
            }
        }
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

C. 人口局 DBA(dba)

推柿子题。

看到的第一反应就是数位 dp,然而看一会你就会发现时间空间都是假的。然后把暴力交上去有 22pts。

考虑如何简化问题。依然从数位 dp 的过程思考,当 limit=0 时,我们此后选数没有任何障碍,也就转化成了一个计数问题:共 a 位,每位上的总和要求为 s,每位能取的范围为 [0,m),求方案数。考虑容斥,钦定这 a 位中有 k 位上的数 m,即让 k 个数 +m,求解组数,此时有 i=1a xi=skm,等价于 i=1a (xi+1)=skm+a,此时满足 xi+11,由插板法可得此时答案为 (ak)(skm+a1a1),那么这个子问题的答案也就有了:k=0a (1)k(ak)(skm+a1a1)

我们设 fa(s)=k=0a (1)k(ak)(skm+a1a1) 表示在无限制条件下位数为 a 要求总和为 s 的方案数,方便后续表示。

此时考虑加上 limit 的限制。由于题目要求 x<n,所以其本身取到 ai 的情况答案是为 0 的不考虑,那么设这一位为 id (从高到低),则其答案为 i=0aid1 fnid(si)

开推:

Ansid(s)=i=0aid1 fnid(si)=i=0aid1k=0nid (1)k(nidk)(sikm+nid1nid1)=k=0nid (1)k(nidk)i=0aid1(sikm+nid1nid1)

我们设 x=aid1,y=skm,p=nid1,则后面一坨可以表示为 i=0x (yi+pp)

结合杨辉三角来表示,可以将其化为:

i=0x (yi+pp)=i=0x (yi+p+1p+1)(yi+pp+1)=(y+p+1p+1)(yx+pp+1)

代回原来的式子,得:

Ansid(s)=k=0nid (1)k(nidk)((skm+nidnid)(skm+nidaidnid))

考虑代回原题,我们每次多固定一位等于原位上的数,那么此时答案即为 Ansn(totsum1),所以总答案为 i=1n Ansi(totsumi1),其中 sumi 表示第 i 位及其高位上的数之和。预处理 n×m 以内的阶乘和逆元,总复杂度 O(nm+n2)

点击查看代码
#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 = 2000 + 5;
const int mod = 1e9 + 7;
int n, m;
int a[N], zc[N], tot;
ll jc[N * N], ny[N * N], 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;
    }
    ll Wc(int n, int m)
    {
        if(n < m) return 0;
        return jc[n] * ny[m] % mod * ny[n - m] % mod;
    }
    short main()
    {
        freopen("dba.in", "r", stdin), freopen("dba.out", "w", stdout);
        m = qr, n = qr;
        fo(i, 1, n) a[i] = qr, tot += a[i];
        jc[0] = ny[0] = 1;
        fo(i, 1, n * m) jc[i] = jc[i - 1] * i % mod;
        ny[n * m] = Wqp(jc[n * m], mod - 2);
        fu(i, n * m - 1, 1) ny[i] = ny[i + 1] * (i + 1) % mod;
        fo(i, 1, n)
        {
            fo(k, 0, n - i)
            {
                int op = (k & 1) ? -1 : 1;
                ans = (ans + (1ll * op * Wc(n - i, k) % mod + mod) % mod * ((Wc(tot - k * m + n - i, n - i) - Wc(tot - k * m + n - i - a[i], n - i) + mod) % mod) % mod) % mod;
            }
            tot -= a[i];
        }
        printf("%lld\n", ans);
        return Ratio;
    }
}
signed main(){return Wisadel::main();}

D. 银行的源起(banking)

不会。

赛时想到找重心分开再找重心,然后发现还有居民就不会了,打了 O(n3) 暴力拿 10pts。

打的依托,T1 上来没思路就很伤,然后想了一个需要打巨大长代码的假思路更伤,结果是导致本来能切两题的场一题没切,暴力还没拿满。

学到的东西太不牢固导致一遇到需要结合多一点东西的题就发懵,维持有效状态的时间短是很重要的一个原因。

huge 晚上来开小会。内务纪律扯🥚不提,但 noip 真可能是我最后一场作为全职 OIer 的比赛了。不说像初见 OI 时那样一定冲金夺银的豪言壮语,多少也要给自己这一年多的时间一个交代,我到底有没有成长,我的实力究竟如何。挂分再怎么不应该也是挂了,没人会听你本来该能多少分,别人看到的只是最终的结果,当然,你自己真正在意的也是。所以拿出最好的状态,打一场酣畅淋漓的比赛才是最好的结果。不知道说这些能不能对自己有点影响,但是真的到了该拼的时候了,加油!


不知道会不会完结撒花~

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