【Coel.题解合集】【最终之落笔】珂艾尔个人题目的题解合集!

2024 年到了,给大家送点新年礼物,顺便给咱的 OI 旅程画上圆满句号w

下面的题解按照题单的顺序排列,如果找不到可以直接用 Ctrl+F 搜索标题喵w


首先是 CJOI (咱的个人赛!)的题目,这些内容全部复制粘贴自这里,懒得再写一次了(

[CJOI-Ex]一道简单的英文题。

这是一个脑筋急转弯。
一般人直接把描述全部翻译完了,就会觉得:“woc 我又不会读心术怎么知道你想的数字是多少?”
(虽然咱希望能有一个和珂艾尔心灵相通的人出现)
但是请注意题目背景:选择性地翻译题面并理解题目含义。据此我们可以得到真正的翻译结果:

题目描述:珂艾尔在心里想了一个数,请输出 the number that Coel thought.
输入格式:本题没有输入。
输出格式:输出只有一行,为 the number that Coel thought.
说明/提示:本题没有数据范围。

因此,只需要输出the number that Coel thought.就可以了。

int main() {
	puts("the number that Coel thought.");
	return 0;
}

[GLFLS 5A][CJOI-A]诈骗大师

本题同时作为国龙模拟赛 A 题。
这道题的标准写法是用单调队列,然而在下造数据能力有限,随便怎么写都可以过,所以不写思维过程了。

int main(void) {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= m; i++)
        cin >> s[i].l >> s[i].r;
    for (int i = 1, j = 1; i <= n; i++) {
            while (j <= m && s[j].l <= a[i])
                j++;
            j--;
            if (s[j].r >= a[i] && s[j].l <= a[i] && !vis[j]) {
                ans.push_back(s[j]);
                vis[j] = true;
        }
    }
    cout << ans.size() << endl;
    for (auto v : ans)
        cout << v.l << ' ' << v.r << endl;
    return 0;
}

[CJOI-B]快速公约数变换

本题灵感来源于知乎上的某个问题,虽然现在找不到了。

因为 gcd(i,j)gcd(j,i) 相等,而 gcd(i,i) 只会出现一次,所以所有出现次数为奇数的数字就是答案,开一个数组 bi 存放数字 i 的出现次数,然后从大到小把答案输出一遍。

int main(void) {
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = 1, x; j <= n; j++) {
            cin >> x, b[x]++;
            maxx = max(maxx, x);
        }
    for (int i = maxx; i >= 1; i--)
        if (b[i] & 1) cout << i << ' ';
    return 0;
}

本题其实是CF582A 的弱化,这个做法仅限于所有 ai 互不相同,如果相同就要用另外的做法了。

[CJOI-C]对 1 的渴求

个人觉得是一个比较“思维好题”的题,然而自己的解法被 Jeslan 的爆了,有点尴尬。

首先,如果数列中有一个 1,由于 gcd(1,x)=1,所以让这个 1 每次和相邻的 1 合并即可,操作次数为 n1

如果 1m 个,那么让 1 和相邻不是 1 的数合并即可,操作次数为 nm,只写这个特判可以得 20 分。

如果不存在 1,则应当尽可能快速地合并出一个 1 来,再按照上面的方法求解。那么,问题转化为找到一个范围尽可能小的区间 [l,r],使得 gcd(al,al+1,,ar)=1

暴力枚举左右端点求区间 gcd 的做法是 O(n2logw)w 为值域) 的,可以得 30 分。

注意到固定左端点后,右端点的增加会使得区间 gcd 具有单调性,因此考虑二分,只枚举左端点,对每个枚举到的左端点二分查找右端点的位置,同时维护区间长度最小值。

此时我们需要一个快速求解区间 gcd 的数据结构,用 ST 表或者线段树均可。

另外可以发现,这个区间其实可以用双指针维护,这样时间复杂度可以降到 O(nlogw),但本题二分已经能过了,所以没必要再优化。

void initST() {
	for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
	for (int i = 1; i <= n; i++) ST[i][0] = a[i];
	for (int j = 1; (1 << j) <= n; j++)
		for (int i = 1; i <= n - (1 << j) + 1; i++)
			ST[i][j] = __gcd(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
}

int query(int l, int r) {
	int t = lg[r - l + 1];
	return __gcd(ST[l][t], ST[r - (1 << t) + 1][t]);
}

signed main(void) {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		if (a[i] == 1) cnt++;
	}
	initST();
	if (cnt != 0) cout << n - cnt, exit(0); //数列存在 1,答案为 n 减去 1 的个数
	if (query(1, n) != 1) cout << "Unhealthy!", exit(0); //无法凑出 1,无解
	for (int i = 1; i <= n; i++) {
		int l = i + 1, r = n;
		while (l <= r) { //二分过程
			int mid = (l + r) >> 1;
			if (query(i, mid) == 1) {
				res = min(res, mid - i);
				r = mid - 1;
			} else l = mid + 1;
		}
	}
	cout << res + n - 1;
	return 0;
}

[CJOI-D]最初的贤者

顺带一提,这场比赛出在原神更新散兵周本之后的两个星期以内。
再顺带一提,我喜欢纳西妲。

这道题其实是一个类似最长上升子序列的动态规划。设 dpi 表示最后摧毁的灭度机为第 i 个时能够得到的最大能量,显然状态转移方程为

dpi=maxxixj,yiyj,aiaj,titj{dpj}+wi

这样写的时间复杂度为 O(n2),可以得到 40 分。

考虑进行优化。回想一下导弹拦截那道题,我们利用了一个单调栈将其优化到了 O(nlogn)。这里当然也是做转移优化,不过用的不是单调栈,而是 CDQ 分治

事实上,导弹拦截本质上是一个二维偏序问题,而 CDQ 分治适用于解决三维偏序的问题。而本题有四个维度,但做法还是一样的,思想就是维度的消除。第一维,排序;第二维,做 CDQ 分治。第三维呢?再套一个 CDQ 分治!最后第四维写一个树状数组,大功告成。

需要注意的是,里层 CDQ 分治计算贡献时,必然是外层 CDQ 分治的右半区间给左半区间求贡献。因此,做外层 CDQ 分治时要顺带着把区间划分给记录下来,对每个点开一个 vis 表示这个点在外层分治的左半区间还是右半区间。

这道题的树状数组是用来求区间最值的,所以修改操作要改成求最大值的操作。另外本题数据有负数,所以树状数组的初始化要设置为负无穷。

套了两层 CDQ 分治,再加上内层的树状数组,时间复杂度为 O(nlog3n)。当然这题还有很多别的解法,比如 CDQ 分治和树套树的嵌套,K-D Tree,Bitset 等等,理论上能求三维偏序的都可以拿来做这道题。

const int maxn = 5e4 + 10;
const ll inf = 1e18;

int n;
int d[maxn], dtop, atop;
ll ans = -inf, dp[maxn];

struct node {
    int a, b, c, d, id;
    ll sum, val;
    bool vis;
    inline bool operator==(const node &x) {
        return a == x.a && b == x.b && c == x.c && d == x.d;
    }
} a[maxn], t1[maxn], t2[maxn];

template<typename T> void gma(T &x, T y) { if (x < y) x = y; }

class Fenwick_Tree { //树状数组
    private:
#define lowbit(x) (x & (-x))
        ll c[maxn];
    public:
        void init() {
            memset(c, -0x3f, sizeof(c));
        }
        void add(int x, ll v) {
            for (int i = x; i < maxn; i += lowbit(i)) gma(c[i], v);
        }
        ll query(int x) {
            ll res = -inf;
            for (int i = x; i; i -= lowbit(i)) gma(res, c[i]);
            return res;
        }
        void toZero(int x) {
            for (int i = x; i < maxn; i += lowbit(i)) c[i] = -inf;
        }
} T;

bool cmp1(node x, node y) { //第一维排序
    if (x.a != y.a) return x.a < y.a;
    if (x.b != y.b) return x.b < y.b;
    if (x.c != y.c) return x.c < y.c;
    return x.d < y.d;
}

bool cmp2(node x, node y) { //第二维排序
    if (x.b != y.b) return x.b < y.b;
    if (x.c != y.c) return x.c < y.c;
    if (x.d != y.d) return x.d < y.d;
    return x.a < y.a;
}

bool cmp3(node x, node y) { //第三维排序
    if (x.c != y.c) return x.c < y.c;
    if (x.d != y.d) return x.d < y.d;
    if (x.a != y.a) return x.a < y.a;
    return x.b < y.b;
}

void init_hash() {
    sort(d + 1, d + n + 1);
    dtop = unique(d + 1, d + n + 1) - d - 1;
    for (int i = 1; i <= n; i++)
        a[i].d = lower_bound(d + 1, d + dtop + 1, a[i].d) - d;
}

void CDQ_inside(int l, int r) { //内层 CDQ 分治
    if (l == r) return;
    int mid = (l + r) >> 1;
    CDQ_inside(l, mid);
    for (int i = l; i <= r; i++) t2[i] = t1[i];
    sort(t2 + l, t2 + mid + 1, cmp3);
    sort(t2 + mid + 1, t2 + r + 1, cmp3);
    for (int i = mid + 1, j = l; i <= r; i++) {
        while (j <= mid && t2[i].c >= t2[j].c) { //计算左边对右边影响
            if (t2[j].vis == false) T.add(t2[j].d, dp[t2[j].id]);
            j++;
        }
        if (t2[i].vis == true) gma(dp[t2[i].id], T.query(t2[i].d) + t2[i].val);
    }
    for (int i = l; i <= mid; i++)
        if (t2[i].vis == false) T.toZero(t2[i].d); //还原树状数组
    CDQ_inside(mid + 1, r);
}

void CDQ_Divide(int l, int r) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    CDQ_Divide(l, mid);
    for (int i = l; i <= r; i++) t1[i] = a[i];
    for (int i = mid; i <= r; i++) t1[i].vis = true; //右半区间记为 true
    sort(t1 + l, t1 + r + 1, cmp2);
    CDQ_inside(l, r);
    CDQ_Divide(mid + 1, r);
}

int main(void) {
    T.init();
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].a >> a[i].b >> a[i].c >> a[i].d >> a[i].val;
        a[i].id = i;
        a[i].c *= -1; //乘个 -1 相当于把符号颠倒一下
        d[i] = a[i].d;
    }
    init_hash(); //离散化第四维,方便树状数组的操作
    sort(a + 1, a + n + 1, cmp1);
    for (int i = 1; i <= n; i++) { //对属性相同点去重(只保留能量为正数的点)
        if (a[i] == a[i - 1]) {
            if (a[i].val > 0) a[atop].val += a[i].val;
        } else a[++atop] = a[i], a[atop].id = atop;
    }
    for (int i = 1; i <= n; i++) dp[i] = a[i].val; //dp 边界处理
    n = atop;
    CDQ_Divide(1, n);
    for (int i = 1; i <= n; i++) gma(ans, dp[i]);
    cout << ans << '\n';
    return 0;
}

接下来是出在其他模拟赛的散题。

[GLFLS 4C]高考数学

莫比乌斯反演的模板题。模板也能被放在模拟赛里面,谢谢 Sherlockk 给的面子

算分母是比较简单的过程,所以先讲一下。
nm 可选数字个数为 k ,可以知道 k=mn+1,那么分母就是 Ck2
接下来推推式子:

Ck2=k!2!(k2)!=k(k1)(k2)...×2×12(k2)(k3)...×2×1=k(k1)2

这样就能以 O(1) 的复杂度求出分母。顺带一提,如果你在物理学到了玻尔原子模型,会发现 Ck2 十分常用。

下面是求分子。不难将其表示为

i=nmj=i+1m[gcd(i,j)=1]

由于 j 的起始值不定,很难操作,所以改写为

i=nmj=nm[gcd(i,j)=1]2

运用一点简单的反演知识,可以得到

i=nmj=nm[gcd(i,j)=1]

=d=1mμ(d)(mdn1d)2

结合数论分块即可求出答案。代码比较简单,也就不放了。

[GLFLS 6φ] 去他的物理实验

分部分分讲解。

20 分做法

我会待定系数!
由于 Qmin=0,也就是说能够使所有点落在同一条直线上,所以我们只需要使用待定系数法:任意选择两个给定的点,代入到 y=kx+b 中解出 kb 即可。
方法很简单,这里就不放参考代码了。

60 分做法

我会三分!
注意到 kb 的取值都会导致 Q 发生变化,而且答案具有单峰性(类似二次函数,顶点的两边都具有单调性),所以我们可以考虑使用三分法,且由于有两个变量,使用三分套三分
这里实际上利用了一个性质:当 k 为定值时,我们可以把 Q 看做是关于 b 的函数,且 Q 的最值点左边单调递减,右边单调递增;当 b 为定值时,也有同样的结果。
具体来说,我们在外层的三分控制 k 的取值,在内层的三分控制 b 的取值即可。下面是 Jeslan 提供的代码:

#include <iostream>
#include <cstring>

const int MAXN = 1e6+5;

int n;
double x[MAXN], y[MAXN];

double getQ(double k, double b) { //当前求得的 k 与 b 对应计算出 Q
	double ret = 0;
	for(int i=1; i<=n; ++i) {
		double temp = x[i] * k + b - y[i];
		ret += temp * temp;
	}
	return ret;
}

double getB(double k) { //当前求得的 k 对应计算出 b,进行内层三分
	double bL = -1e4, bR = 1e4;
	while(bR - bL > 1e-4) {
		double bML = bL + (bR - bL) / 3.0;
		double bMR = bR - (bR - bL) / 3.0;
		double fL = getQ(k, bML);
		double fR = getQ(k, bMR);
		if(fL <= fR) bR = bMR;
		if(fL >= fR) bL = bML;
	}
	return bL;
}

int main() {
	scanf("%d", &n);
	for(int i=1; i<=n; ++i) {
		scanf("%lf %lf", x+i, y+i);
	}
	double kL = -1e4, kR = 1e4;
	while(kR - kL > 1e-4) { //外层三分
		double kML = kL + (kR - kL) / 3.0;
		double kMR = kR - (kR - kL) / 3.0;
		double fL = getQ(kML, getB(kML));
		double fR = getQ(kMR, getB(kMR));
		if(fL <= fR) kR = kMR;
		if(fL >= fR) kL = kML;
	}
	printf("%.3f %.3f", kL, getB(kL));
	return 0;
}

该方法的时间复杂度为 O(nlog3A)A 为值域),已经是很优秀的做法了。

满分做法

我是 whk 高手!
本题实际上考察的是一元线性回归方程,考虑到赛时选手都没学过,所以用来考验一下大家的推式子能力。
推导过程在各位的数学课本上都有,就不直接写了。
结论为

k=i=1n(xix¯)(yiy¯)i=1n(xix¯)2,b=y¯kx¯

代入求解即可。

int main(void) {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> x[i] >> y[i];
		ave_X += x[i], ave_Y += y[i];
	}
	ave_X /= n, ave_Y /= n;
	for (int i = 1; i <= n; i++) {
		up += (x[i] - ave_X) * (y[i] - ave_Y); //求 k 的分子
		dn += (x[i] - ave_X) * (x[i] - ave_X); // 求 k 的分母
	}
	k = up / dn, b = ave_Y - k * ave_X;
	cout << fixed << setprecision(5) << k << ' ' << b;
	return 0;
}

[GLFLS 7F] 阿洛娜导航

玩 BA 玩的。
本来想写一个小剧场来解释,然而时间不够。
几个特殊点都是送分的,不解释,直接看 n2×105

这个数据范围是标准的单源最短路算法,考虑做转化。借用网络流中“拆点”的原理,我们可以把代表报名费的点权转换成边权

具体怎么做?假想有一个点编号 0,让每个点都和它相连,权值等于报名费。这个时候,要求第 i 个点的最小花费,只需求出该点到 0 号点的最短路即可。

这时求的是多源点、单汇点的最短路,考虑对称性,可以反过来求 0 号点到其他点的最短路,这样就能划归到单源最短路径上了。

后面两个 0 分点是用来卡 SPFA 的,用 Dijkstra 就可以了,当然如果你有高级的 SPFA 优化算法也可以用。

int main(void) {
	cin >> n >> m;
	memset(head, -1, sizeof(head));
	for (int i = 1, x; i <= n; i++) {
		cin >> x;
		add(0, i, x); //连接 0 号点
	}
	for (int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w);
		add(v, u, w);
	}
	dijkstra(0);
	for (int i = 1; i <= n; i++)
		cout << dis[i] << ' ';
	return 0;
}

本文作者:Coel's Blog

本文链接:https://www.cnblogs.com/Coel-Flannette/articles/17939367

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   秋泉こあい  阅读(41)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 アイノマテリアル (feat. 花里みのり&桐谷遥&桃井愛莉&日野森雫&MEIKO) MORE MORE JUMP!
アイノマテリアル (feat. 花里みのり&桐谷遥&桃井愛莉&日野森雫&MEIKO) - MORE MORE JUMP!
00:00 / 00:00
An audio error has occurred.