Logo cxm1024 的博客

博客

标签
暂无

CFR-746(Div.2) 解题报告

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-10-09 19:57:28

更好的阅读体验

VP做出来一道,补题又做出来3道。

A. Gamer Hemose

$Problem$

你有 $n$ 个武器,要打一个体力为 $H$ 的敌人,第 $i$ 个武器可以对敌人造成 $a_i$ 的伤害,每把武器不能连续使用两次,问至少需要多少次才能打败敌人。

$t\le 10^5,\sum n\le 2\times 10^5$

$Solution$

(读错题意调了半天)

肯定是最厉害的武器和次厉害的武器轮番上阵,把它们看作一组,首先算要用多少组,剩余的再单独处理。

时间复杂度 $O(1)$。

B. Hemose Shopping

$Problem$

你有一个数组,每次操作只能将距离 $\ge x$ 的两个数交换,问能否排好序。

$t\le 10^5,\sum n\le 2\times 10^5$

$Solution$

由于每次距离只能 $\ge x$,中间可能有一段区间是无论如何也动不了的,为 $[n-m+1,m]$。假设不用考虑这段区间,则剩下的分成左右两边,每次只能交换左右两边的数,是否能排好序呢?能。我们肯定能交换左右两边的数,所以考虑如何交换同一边的数(假设是 $a_x,a_y$),我们需要一个在另一边的辅助变量 $t$,就可以用我们刚学编程是学的用辅助变量来交换变量的方法交换,而 $t$ 的值也不会改变。

如此,两边的就不用管了,反正总能排好序。接下来考虑中间的部分,因为完全动不了,我们就要求它们已经排好序了。这样就结束了?不。(我就被这个坑了)它们还需要在排好序的数组里处于正确的位置,即,对于排好序的数组 $b$,$\forall i\in[n-m+1,m],a_i=b_i$。

C. Bakry and Partitioning

$Problem$

给你一棵有点权的树,问是否能割断至少 $1$ 条、至多 $k-1$ 条边,将树分成若干个连通块,使得每个连通块中点权的异或和相等。

$t\le 5\times 10^4,\sum n\le 2\times 10^5$

$Solution$

如果异或和是 $0$,随便割一条边即可,因为两边的异或和相等,异或起来的结果才能等于 $0$。

如果疑惑和不是 $0$,而是 $x$,我们就需要将树分成三个异或和都为 $x$ 的连通块。为什么不是五个、七个?因为可以合并三个异或和都为 $x$ 的连通块得到一个异或和为 $x$ 的连通块。具体实现中跑一边 dfs 即可,跑的过程中一发现出现异或和为 $x$ 的连通块,马上把它分割出去,统计分割了多少次,如果多于两次就成立,否则不成立。

D. Hemose in ICPC ?

$Problem$

这是一道交互题。 给你一棵有边权的树,但你不知道边权是多少,每次你可以询问一个点集,会得到这个点集中距离最远的两个点的距离(即直径)。距离定义为路径中所有边权的 $\gcd$。你需要在 $12$ 次询问以内输出整棵树直径的两个端点。

$n\le 10^3$

$Solution$

有一个很重要的结论,$a\ge gcd(a,b)$,这告诉我们树的“直径”一定只有一条边,所以问题转化为我们要求得最长的边的两个端点。我们先花费一次询问得知整棵树的直径(即最长的边),设它为 $x$。

假设我们以 $1$ 号结点为根,那么每一条边唯一对应一个点,所以我们用一个点来代表这个点与它父亲结点之间的边。考虑我们先对所有点按照 dfs 序排序,然后二分选相邻的点集(一定要加上前面的),这样它的直径一定是这个点集中最长的边,如果它是 $x$,那么递归再这段,否则递归后面的段(也加上前面的)。

为什么选的点要相邻呢?以下图为例,如果我们问 ${1,3,4}$,它返回 $10$,我们也不知道是否真的有一条边的权值等于 $10$,这对我们获知答案没有什么意义。

EDU-CFR-114(Div.2) 解题报告

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-10-12 21:42:52

更好的阅读体验

赛时AC 2道题,赛后补题做出来一道(赛时交了4发,赛后交了十几发才过,太残忍了)

总体难度比较高,可能题解会比较冗长

A. Regular Bracket Sequences

$Problem$

输入 $n$,输出 $n$ 个互不相同的、合法的、长度为 $2n$ 的括号序列。

$t\le 50,n\le 50$

$Solution\ 1$

update: 我的解法非常复杂,可以直接看 $Solution\ 2$ CF官方题解的做法(想看看我的憨批做法也行)

考虑一开始在 $(0,0)$ 点,左括号往右上走,右括号往右下走,则合法的括号序列一定在 $x$ 轴之上。现在考虑最简单的括号序列:(((...(())...))),即 $${+1,+1,...,+1,+1,-1,-1,...,-1,-1}$$ 展现在坐标系上,就是一个山峰形,那么我们考虑将山峰的顶砍一刀,让它凹进去,变成 $${+1,+1,...,+1,-1,+1,-1,...,-1,-1}$$ 这样就得到了下一种合法序列,再下一种就再把左边的山峰砍一刀(自始至终都不管右边),每次砍左边的山峰,由于最初的山峰高度为 $n$,每砍一刀左边的山峰高度 $-1$,看 $n-1$ 刀的最终高度为 $1$,加上最初的单峰山,就得到了 $n$ 种合法括号序列。

实现上,把最初的 ${+1,+1,...,+1,+1,-1,-1,...,-1,-1}$ 序列存到数组里,每次交换一对 $+1,-1$ 变成 $-1,+1$,再转换成括号序列输出即可。

$Solution\ 2$

CF官方题解,让我觉得我的解法太复杂了。直接引用英文原版,肯定能看懂。

start with the sequence ()()()()...; merge the first $4$ characters into one sequence to get (())()()...; merge the first $6$ characters into one sequence to get ((()))()...; and so on.

B. Combinatorics Homework

$Problem$

你有 $a$ 个 A、$b$ 个 B、$c$ 个 C,问能否组成“恰好有 $m$ 对相邻两个字符相同”的字符串。

$t\le 10^4,a,b,c,m\le 10^8$

$Solution$

显然,只要 $m$ 在“相邻两个字符相同的对数”的最大值和最小值之间,就一定可以。最大值很简单,若干个 A、若干个 B、若干个 C 这样排,$(a-1)+(b-1)+(c-1)$ 就行(别忘了 $\max(0,...)$),问题是最小值怎么求呢?我们可以发现一种典型情况:最大值太大,两个较小值用尽浑身解数拆散,最大值也还有剩余;还有一种情况就是两个较小值不需要用全部的力气拆散,数量最多的字母已经被拆得一对也没有了。针对后者,我们很轻松的就能猜出来一定总能互相拆的一对也不剩,无需多考虑,而针对前者,需要算出来两个数量较少的字母用尽浑身解数能把数量最大的拆得还剩几对。我们发现 ABABAAAAAABAABAA 是一样的,于是我们又可以得到,尽可能多的拆散,就是把较少的两种字母分散开插进最多的字母里,怎么插无所谓,只要它们自己不连在一起就行。这样我们能够得到,针对前一种情况,能组成的对数的最小值为 $max-1-(all-max)$(感性理解一下),将它和 $m$ 比较即可。

C. Slay the Dragon

$Problem$

你有 $n$ 个勇士,每个勇士有一定的能力值 $a_i$,每组数据给出一条龙的防御力 $x$ 和攻击力 $y$,你需要派出一个能力值 $\ge x$ 的勇士来攻击龙,剩余的勇士防御,防御的勇士能力值之和必须 $\ge y$。你每次可以花费 $1$ 的代价将一个勇士的能力值提高 $1$,对于每组数据,回答最少花费。注意:每组数据相互独立,每组数据提高的勇士能力值不会保存到下一组数据。

$Solution$

这题竟然卡常!!!(痛骂 Codeforces)可能需要用 ios::sync_with_stdio(false) 才能过

很容易想到,每次可以用比龙的防御力小的能力值最大的人去打龙(给他提高一点),或是用第一个比龙的防御力大的人去打龙(不需要提高),然后再按需提高剩下的人(提高谁都没有区别,只需要考虑他们的和就行),取这两种方案中花费较小的一种。然后你就会发现细节巨多,当调了 n 遍终于调出错来时,你就会发现

Time limit exceeded on test 6

加上 ios::sync_with_stdio(false) 或快读可过(如果你是 scanf 党,当我没说)。

EDU-CFR-115(Div.2)解题报告

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-10-14 15:48:10

更好的阅读体验

赛时AC 3道,补题做出来一道

A. Computer Game

$Problem$

有一个 $2\times n$ 的01矩阵,1为障碍,你要从 $(1,1)$ 走到 $(2,n)$,每一步只能向右、上、下、右上、右下走,问能不能走到。

$t\le 100,n\le 100$

$Solution$

如果有一列的两个数都是1,则一定会被堵住,否则一定能到,因为每一列至少有1个0,而我们可以斜着走,所以一定可以从一列走到下一列。

B. Groups

$Problem$

有 $n$ 个学生($n$ 是偶数),每个学生在星期一到星期五会有若干天空闲,问能否将学生平均分成两组,使得每一组能够挑选一天组织社团活动(两组选的日子不能相同)。

$t\le 10^4,\sum n\le 10^5$

$Solution$

由于一周只有五天,考虑枚举选的是哪两天,然后判断可不可行。判断就需要一点脑洞了,我的方法是如果两天的空闲学生人数都 $\ge n/2$,且交集能够覆盖所有学生(即每个学生都在这两天中至少有一天空闲),就一定可行。

C. Delete Two Elements

$Problem$

给你一个长度为 $n$ 的序列,问有多少种选出两个数的方案使得删除它们后平均值不变。

$t\le 10^4,\sum n\le 2\times 10^5,a_i\le 10^9$

$Solution$

要想删除后平均值不变,取出的两个数的平均值必须等于全部的平均值,即两个数的和必须等于全部平均值的两倍,如果数列的平均值 $\times 2$ 不是整数则一定不可行(选出的两个数的和一定是整数),我们如果已经选出了一个数,就可以知道另一个数应该是多少,于是我们可以想到开一个桶,但 $10^9$ 开不了桶怎么办?用 map 即可。

D. Training Session

$Problem$

教练有 $n$ 道题,每道题有一个算法主题 $x$ 和难度 $y$,你需要选出恰好 $3$ 道题使得 $x$ 互不相同 $y$ 互不相同。问有多少种选法。保证没有两个完全相同的题。

$t\le 50000,\sum n\le 2\times 10^5,x,y\le n$

$Solution$

考虑用总方案数减去不合法方案数,不合法的方案就是有两个题的 $x$ 相同,有两个题的 $y$ 相同,表现在坐标系上,每一个点的贡献就是横坐标相同的点数$\times$纵坐标相同的点数,可以开两个桶维护。

EDU-CFR-53(Div.2)解题报告

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-10-14 15:50:48

更好的阅读体验

A. Diverse Substring

$Problem$

定义一个字符串为多变的,当且仅当字符串中没有一个字符的出现次数严格大于 $n/2$。给定一个只由小写字母构成的字符串,问是否能找出一个多变的字串,如果能,任意输出一个。

$n\le 1000$

$Solution$

只有两个不同字符的字符串时多变的,所以只要给定的字符串中包含至少两个字符就一定可以,输出相邻两个不同字符即可。

B. Vasya and Books

$Problem$

有一摞书,从上到下依次编号为 $a_1,a_2,a_3...$,现在 Vasya 想把它们分 $n$ 次挪到别的地方,每次他会告诉你他想挪的书的编号,如果这本书还没有被挪动,他将会把这本书以上的书(包括这本书)一块搬走。对于每次挪动,回答他搬了多少本书(没搬输出 0)。

$n\le 2\times 10^5$

$Solution$

开一个桶记录每一本书的位置,记录当前搬到那个位置了,每搬一次就更新一下,减一下即可。

C. Vasya and Robot

$Problem$

有一个机器人,初始在 $(0,0)$ 点,给你一个长度为 $n$ 的操作序列,每次让机器人向上、下、左、右中的一个方向走一步,你需要修改连续的一段操作序列(这一段中的操作不必全部都修改),使它最终走到 $(x,y)$ 点,问修改的操作序列的最短长度。(不是很严谨,序列长度的详细定义见原题面)

$n\le 2\times 10^5$

$Solution$

如果修改一个长度为 $len$ 的序列可以使它走到终点,那么一定存在一种长度为 $len+1$ 的方案也能走到终点(大不了就加一个空修改),于是我们发现这是可以二分的。二分最终修改的长度 $len$,对于一个长度,我们需要 $O(n)$ 判断是否可行,怎么判断呢?对于每一段长度为 $len$ 的修改,判断不考虑这段修改后机器人会走到哪个位置,再从这个位置开始,判断随便走 $len$ 步能否走到终点(反正允许修改这一段,那想怎么改就怎么改)。

实现上,我们维护一个走的位置的前缀和和后缀和,就可以 $O(1)$ 判断去除这段修改后机器人会走到那个位置,然后再考虑一些细节问题即可。

D. Berland Fair

$Problem$

有一个人前来买东西。Polycarp 带了 $T$ 元钱,去逛商铺。有 $n$ 家商铺顺时针排成一圈,编号 $1-n$,第 $i$ 个商铺卖一种价格为 $a_i$ 的商品(数量有无限个)。Polycarp 从一号商铺开始,只要钱足够,就买一件商品,否则跳过这家店,直到他一个商品也买不了为止,问他一共会买多少件商品。

$n\le 2\times 10^5,T\le 10^{18},a_i\le 10^9$

$Solution$

先算出来买一圈要花多少钱,让他买尽可能多的圈。为什么他买不了下一圈了呢?一定是到某一个商铺是他买不起了。现在买不起,以后也不可能买的起,于是直接把这个商铺删掉,不考虑。于是我们暴力跑一圈,看看是哪些商铺买不起,把它们都删掉,删完后再让他转圈,知道所有的商品全都被删完为止。由于每一次至少删掉一个商品,最多删 $n$ 次,每次 $O(n)$,总时间复杂度为 $O(n^2)$。(别问我为什么能过 $2\times 10^5$,我也不知道)(经过同学推导,复杂度为 $O(n\log(n))$)

P3383【模板】线性筛 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-10-21 15:42:57

这道题是线性筛的模板题,所以我们考虑怎么不用线性筛。

我们都知道有一种筛法叫埃拉托色尼筛,简称埃氏筛,它比线性筛好写,也更好理解,但它过不了这道题,那怎么办呢?我们可以用 $bitset$ 代替 bool 数组来进行优化,这造成的常数优化非常显著,以至于开了 ios::sync_with_stdio(false) 之后能轻松过掉这道题,和线性筛的时间总差距只有 $0.5s$,可以说是非常优秀了。

AC 代码:

#include<bits\/stdc++.h>
using namespace std;
const int MAXN=100000000;
int cnt=0,prime[6000000];
bitset<100000000> p;
void init() {
	p.flip();p[1]=0;
	for(int i=2;i<MAXN;i++) {
		if(p[i]) {
			prime[++cnt]=i;
			for(int j=i+i;j<MAXN;j+=i)
				p[j]=0;
		}
	}
}
int main() {
    ios::sync_with_stdio(false);
	init();
	int n;
	cin>>n>>n;
	while(n--) {
		int x;
		cin>>x;
		cout<<prime[x]<<endl;
	}
	return 0;
}

背包DP学习笔记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-01-22 14:31:18

01背包

由于01背包太过经典,所以一定要把每一个细节理解透彻!

有 $n$ 个物品和一个容量为 $m$ 的背包,每个物品有体积 $w_i$ 和价值 $v_i$,求用这个背包所能装下的最大价值。

设 $f_{i,j}$ 表示只考虑前 $i$ 个物品,体积不超过 $j$ 的最大价值。如果我们算完了前 $i-1$ 个物品的所有结果,那么第 $i$ 个物品有选和不选两种情况。如果不选,则结果为 $f_{i-1,j}$;如果买,则:由于选了 $i$ 后体积不超过 $j$,那么选 $i$ 之前体积就不能超过 $j-w_i$,而选了 $i$ 之后获得的价值就多了 $v_i$,所以结果为 $f_{i-1,j-w_i}+v_i$。这样,我们就得出了经典的状态转移方程:$f_{i,j}=max(f_{i-1,j},f_{i-1,j-w_i}+v_i)$。

接下来,我们再考虑一些细节。对于 $f$ 数组的初始化,我们可以将所有的方案全部赋值为 $0$,因为无论考虑多少物品,无论体积不超过多少,都一定有一种符合要求的方案:一个也不选。这时的价值就是 $0$。

然后就到了经典的空间优化:我们可以发现 $f_i$ 这一行只与 $f_{i-1}$ 这一行有关,所以我们可以将 $i$ 这一维省略。这样,当我们准备求 $f_j$ 时,我们要求 $f_j$ 和 $f_{j-w_i}$ 都还没有被更新过。因为我们正准备更新 $f_j$,所以第一个要求可以保证,那么怎么保证 $f_{j-w_i}$ 没有被更新过呢?答案就是倒序更新(这样就在更新 $f_j$ 之后才会更新到 $f_{j-w_i}$ 了)。

接下来还有一个经典的常数优化:因为当 $j<w_i$ 时,转移方程中 $f_{j-w_i}$ 不存在,不需要考虑更新,所以 $j$ 必须大于等于 $w_i$,也就是倒序枚举时 $j$ 只需要从 $m$ 枚举到 $w_i$。

所以,我们就得到了经典的01背包代码:

for(int i=1;i<=n;i++)
	for(int j=m;j>=w_i;j--)
		f[j]=max(f[j],f[j-w[i]]+v[i]);
cout<<f[m]<<endl;

例题:采药

正好填满的01背包

是一个01背包经典的变形,题意基本与01背包相同,但要求背包必须填满。

这时,$f$ 数组的意义发生了一点变化:$f_{i,j}$ 表示只考虑前 $i$ 个物品,体积恰好为 $j$ 的最大价值。

但是,仔细推理一下,就会发现,状态转移方程和01背包一模一样,空间优化和常数优化也都通用。那不一样的地方在哪里呢?答案是初始化。由于要求体积恰好为 $j$,所以当 $j\ne 0$ 时,不允许一个也不选,所以初始化为负无穷(表示目前没有任何方案满足条件),当 $j=0$ 时,才存在一个也不选的方案,这时才能初始化为 $0$。

代码:

memset(f,-0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
	for(int j=m;j>=w[i];j--)
		f[j]=max(f[j],f[j-w[i]]+v[i]);
cout<<f[m]<<endl;

二维费用背包

有两维费用(如:一个事件既要消耗时间也要消耗金钱,获得一定价值)的01背包。

将01背包多开两维费用,其他完全相同。

代码:

\/\/背包第一维容量为m,背包第二维容量为t
for(int i=1;i<=n;i++)
	for(int j=m;j>=w1[i];j--)
		for(int k=t;k>=w2[i];k--)
			f[j][k]=max(f[j][k],f[j-w1[i]][k-w2[i]]+v[i]);
cout<<f[m][t]<<endl;

例题:榨取kkksc03

完全背包

又是一个经典模型,也必须要理解透彻。题意与01背包基本相同,但每个物品能选无数遍。

同样设 $f_{i,j}$ 表示只考虑前 $i$ 种物品,体积不超过 $j$ 的最大价值。这时如何更新呢?如果我们不选这个物品,那么结果为 $f_{i-1,j}$;如果选,那么结果为 $f_{i,j-w_i}+v_i$。这是为什么呢?在01背包中,我们当前要选 $i$,那么选这个 $i$ 之前,只能考虑前 $i-1$ 个物品,所以要从 $f_{i-1,j-w_i}$ 转移,但是在完全背包中,每个物品可以选无数次,所以选这个 $i$ 之前,$i$ 也是可以选的,所以要从 $f_{i,j-w_i}$ 转移而来。这样,我们就得到了最终的转移方程:$f_{i,j}=max(f_{i-1,j},f_{i,j-w_i}+v_i)$。

同01背包一样,我们也可以省略掉 $i$ 这一维。这时,当我们求 $f_j$ 时,要求变成 $f_j$ 还没有更新,而 $f_{j-w_i}$ 已经更新过了(因为我们要用的是 $f_{i,j-w_i}$ 而不是 $f_{i-1,j-w_i}$)。同样,第一个要求能直接满足,对于第二个要求,我们只需要正序枚举 $j$ 即可。所以,最终转移方程与01背包一样,但 $j$ 的枚举顺序变成了正序。

代码:

for(int i=1;i<=n;i++)
	for(int j=w[i];j<=m;j++)
		f[j]=max(f[j],f[j-w[i]]+v[i]);
cout<<f[m]<<endl;

例题:疯狂的采药

多重背包

题意与01背包基本相同,但每种物品能选 $x_i$ 次。

一个很容易想到的思路为将一种物品选 $x$ 次转换成 $x$ 个完全相同的物品,再做01背包。

这样的复杂度显然不够优秀,所以我们考虑优化。我们希望将每个物品选 $x$ 次转换成若干个物品,使得无论想选多少次都能用这若干个物品凑出来。一个较为明显的做法就是二进制分解。例如,我们有一个物品能选20次,我们就将它分解成一个 $1$ 倍物品、一个 $2$ 倍物品、一个 $4$ 倍物品、一个 $8$ 倍物品和一个 $5$ 倍物品(几倍物品指的是体积和价值都为原物品的几倍)。易证,这一定满足我们的条件。这样,我们就将一个物品选 $x$ 次分解成了 $\log(x)$ 个物品,然后再跑一遍01背包即可。

代码:

for(int i=1;i<=n;i++)
	for(int tmp=1;x[i];tmp*=2) {
		int num=min(tmp,x[i]);
		int wt=w[i]*num,vt=v[i]*num;
		for(int j=m;j>=wt;j--)
			f[j]=max(f[j],f[j-wt]+vt);
		x[i]-=num;
	}
cout<<f[m]<<endl;

例题:宝物筛选

混合背包

将01背包、完全背包和多重背包缝合在一起的问题。

思路很简单,无需多讲解,分别考虑即可。形式为:

for(枚举物品)  {
	if(01背包)
		01背包代码
	else if(完全背包)
		完全背包代码
	else
		多重背包代码
}

实际上,01背包和多重背包可以共用多重背包的代码,因为01背包可以当成只能取一次的多重背包。

例题:樱花

核心代码:

for(int i=1;i<=n;i++) {
	if(x[i]==0) {\/\/完全背包
		for(int j=w[i];j<=m;j++)
			f[j]=max(f[j],f[j-w[i]]+v[i]);
	}
	else {\/\/01背包和多重背包
		for(int tmp=1;x[i];tmp*=2) {
			int num=min(tmp,x[i]);
			int wt=w[i]*num,vt=v[i]*num;
			for(int j=m;j>=wt;j--)
				f[j]=max(f[j],f[j-wt]+vt);
			x[i]-=num;
		}
	}
}
cout<<f[m]<<endl;

至此,基本模型已经讲完,希望读者能把每一个步骤都理解透彻!

谢谢观看

P1177快速排序 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-03-09 11:24:53

众所周知,A+B Problem 的题解里充斥着各种奇奇怪怪的做法,那为什么这道题没有呢?于是就有了这篇题解。

1.set大法好

我们都知道,STL 有 sort 和 priority_queue 给我们用,但却很少人想到 set 也是可以排序的。由于有重复元素,所以这里使用 multiset。

#include<bits\/stdc++.h>
using namespace std;
int main() {
	int n,x;
	multiset<int> s;
	cin>>n;
	while(n--) {cin>>x;s.insert(x);}
	for(int t:s) cout<<t<<" ";
	return 0;
}

虽然非常“巧妙”,但是这使用了 STL,违背了题目要求,于是:

2.桶排序

我们都知道,桶排序是非常快的,只需要 $O(N)$ 的时间和 $O(a_i)$ 的空间,然而这题 $a_i$ 最大到了 $10^9$,面对这种情况,最常见的选择就是是离散化(迫真)。于是,我们先对数组离散化一下,再把数组中元素的离散化结果扔到桶里面,最后扫一遍桶中元素即可。

#include<bits\/stdc++.h>
using namespace std;
int n,a[100010],b[100010],cnt=0;
int t[100010];
int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		b[++cnt]=a[i];
	}
	sort(b+1,b+cnt+1);
	cnt=unique(b+1,b+cnt+1)-b-1;
	for(int i=1;i<=n;i++) {
		int tmp=lower_bound(b+1,b+cnt+1,a[i])-b;
		t[tmp]++;
	}
	for(int i=1;i<=cnt;i++)
		while(t[i]--)
			cout<<b[i]<<" ";
	cout<<endl;
	return 0;
}

你可能会问:这不是还是需要 sort 吗?没错,但你还是不能否认桶排序就是快(实际上,这是本篇题解中最快的做法了)。

但是,使用了 sort 确实违背了题目要求,于是:

3.动态开点权值线段树

这题不是显然可以用权值线段树做吗。。。由于数据范围比较大,要么离散化要么动态开点。如果离散化,就需要 sort,这违背了我们的初衷,所以我们使用动态开点。

首先依次加入 $n$ 个数,接下来每次询问排名为 $i$ 的数即可,时间复杂度 $O(nlog(n))$。

#include<bits\/stdc++.h>
using namespace std;
const int inf=1e9;
int n,tot=0,root;
struct tree{int num,l,r,lson,rson;} t[2000010];
#define mid (t[now].l+t[now].r)\/2
int newnode(int l,int r) {
	t[++tot].l=l,t[tot].r=r;
	return tot;
}
void change(int &now,int x,int l=-inf,int r=inf) {
	if(!now) now=newnode(l,r);
	if(l!=r) {
		if(x<=mid) change(t[now].lson,x,l,mid);
		else change(t[now].rson,x,mid+1,r);
	}
	t[now].num++;
}
int search(int now,int k) {
	if(t[now].l==t[now].r) return t[now].l;
	int val=t[t[now].lson].num;
	if(k<=val) return search(t[now].lson,k);
	else return search(t[now].rson,k-val);
}
int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		int x;
		cin>>x;
		change(root,x);
	}
	for(int i=1;i<=n;i++)
		cout<<search(root,i)<<" ";
	cout<<endl;
	return 0;
}

这种做法非常完美的满足了题目的要求:不用任何 STL。完结撒花!

超实用代码前缀

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2020-02-22 13:08:07

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<deque>
#include<utility>
#define DEBUG fprintf(stderr, "Passing function [%s] line %d\n", __FUNCTION__,__LINE__);
#define DE(sss) fprintf(stderr, "Passing function [%s] line %d  %s=", __FUNCTION__,__LINE__,#sss);cout<<sss<<endl;
using namespace std;
int main()
{
    return 0;
}
  • 在某一行输入 DEBUG(不用加分号),运行到那儿会输出当前所在函数和 DEBUG 所在的行号(调试用)
  • 在某一行输入 DE(···)(括号里是任意变量名)(不用加分号),运行到那儿不仅会有和 DEBUG 相同的效果,并输出这个变量当前的值(调试用)

圆锥体积公式推导

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2020-03-31 16:03:01

把圆锥沿高分成$\color{orange}n$份

每份高 $\color{brown}=\frac{\color{red}h}{\color{orange}n}$

第$\color{green}k$份半径 $\color{brown}=\frac{\color{green}k\color{blue}r}{\color{orange}n}$

第$\color{green}k$份底面积 $\color{brown}=\frac{\color{purple}\pi\color{green}k^2\color{blue}r^2}{\color{orange}n^2}$

第$\color{green}k$份体积 $\color{brown}=\frac{\color{purple}\pi\color{red}h\color{green}k^2\color{blue}r^2}{\color{orange}n^3}$

总体积 $\color{orange}n$ 份

$=\color{brown}\sum\limits_{{\color{green}k}=\color{pink}1}^{\color{orange}n}\frac{\color{purple}{\pi}\color{red}h\color{green}k^2\color{blue}r^2}{\color{orange}n^3}$

$\color{brown}=\frac{\color{purple}{\pi}\color{red}h\color{pink}(1^2+2^2+3^2+4^2+...+\color{orange}{n}^2)\color{blue}r^2}{\color{orange}n^3}$

$\color{brown}∵\color{pink}1^2+2^2+3^2+4^2+..\color{orange}.+n^2\color{brown}=\frac{\color{orange}n(n+1)(2n+1)}{\color{pink}6}$

$\color{brown}∴$ 总体积 $\color{orange}n$ 份

$\color{brown}=\frac{\color{purple}{\pi}\color{red}h\color{blue}r^2\color{orange}n(n+1)(2n+1)}{\color{pink}6\color{orange}n^3}$

$\color{brown}=\frac{\color{purple}{\pi}\color{red}h\color{blue}r^2\color{orange}(1+\frac{1}{n})(2+\frac{1}{n})}{\color{pink}6}$

$\color{brown}∵$ 当$\color{orange}n$越来越大,总体积越接近圆锥体积,$\frac{\color{pink}1}{\color{orange}n}$趋于$\color{pink}0$

$\color{brown}∴\lim\limits_{\color{orange}n\color{brown}\to\color{purple}∞}\frac{\color{purple}{\pi}\color{red}h\color{blue}r^2\color{orange}(1+\frac{1}{n})(2+\frac{1}{n})}{\color{pink}6}=\frac{\color{purple}{\pi}\color{red}h\color{blue}r^2}{\color{pink}3}$

因为圆柱体积 $\color{brown}=\color{tan}S\color{red}h\color{black}\color{brown}=\color{purple}{\pi}\color{red}h\color{blue}r^2$

所以圆锥体积是与它等底等高的圆柱体积的$\color{pink}\frac{1}{3}$,也就是$\Large\color{pink}\frac{1}{3}\color{tan}S\color{red}h$。

题解 P2419 【[USACO08JAN]Cow Contest S】

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2020-07-19 13:32:18

一种不是拓扑排序也不是Floyd的玄学算法

目的

就拿样例来说吧:

Uh1R2D.md.png

我们其实可以从图中获得更多信息,比如1比5厉害等,所以我们希望它能把我们所有的已知信息都表现出来,像这样:

Uh1Wxe.png

然后就可以方便地做判断了:哪个点的入度和出度之和 $=n-1$ ,哪个点就可以确定排名。 在实际应用中,我们为了方便,再把每个点都和自己连一条边(反正不用任何图论算法,连了也没关系),就可以直接判断哪个点的入度和出度之和 $=n$ 了。

具体方法

先用矩阵存图,1指向2就把第一行第二列变成1,以此类推,得到:

截屏2020-07-19 下午1.18.00.png

然后要变成第二张图的样子(把所有已知信息表示出来),具体步骤: 如果$(x,y)$点有标记,$(y,z)$点也有标记,那么$(x,z)$点就可以打标记。

为什么呢?因为$(x,y)$点有标记表示$x$比$y$厉害,$(y,z)$点有标记表示$y$比$z$厉害,$x$比$y$厉害,$y$比$z$厉害,$x$就比$z$厉害,所以$(x,z)$点就可以打标记。

最后,要如何判断点i能否和每个点都连边呢?其实很简单:只需要把第i行和第i列上的点都加起来,再减去1(点$(i,i)$被计算了两次),看是否为n就行。

为什么呢?因为第$i$行的标记表示$i$比别“人”厉害,第$i$列的标记表示别“人”比$i$厉害,知道几个“人”比$i$厉害,几个“人”比$i$蒻,就可以知道i的排名了。

算法比较难理解,有耐心的话可以仔细画画想想。


附上AC代码:

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast")
#pragma GCC optimize("inline")
using namespace std;
bool a[110][110];
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		a[x][y]=1;
	}
	for(int i=1;i<=n;i++)
		a[i][i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(a[i][j])
			{
				for(int k=1;k<=n;k++)
					if(k!=i&&a[k][i])
						a[k][j]=1;
				for(int k=1;k<=n;k++)
					if(k!=j&&a[j][k])
						a[i][k]=1;
			}
	int num=0;
	for(int i=1;i<=n;i++)
	{
		int ans=0;
		for(int j=1;j<=n;j++)
		{
			ans+=a[i][j];
			ans+=a[j][i];
		}
		if(ans-1==n)
			num++;
	}
	cout<<num<<endl;
	return 0;
}
共 23 篇博客