0
点赞
收藏
分享

微信扫一扫

关于『 一月の集训 』

得一道人 2022-01-31 阅读 29
c++

关于『 一月の集训 』

A   B   O   U   T J   A   N   U   A   R   Y \tt A \ B \ O \ U \ T \quad J \ A\ N\ U\ A\ R \ Y A B O U TJ A N U A R Y

关于一月的集训,在这里做一个了结总结。

很乱是吧,同感


1.分治

    D   I   V   I   D   E   A   N   D C   O   N   Q   U   E   R \tt \ \ \ D \ I \ V \ I \ D \ E \quad \ A \ N \ D \quad C \ O \ N \ Q \ U \ E \ R    D I V I D E A N DC O N Q U E R

综上,分治的基本思想为:

分治的步骤:

例题:
  1. 快速排序 ( Q u i c k l y _ s o r t \tt Quickly\_sort Quickly_sort)
  1. 二路归并排序 ( M e r g e _ s o r t \tt Merge\_sort Merge_sort)
  1. 求逆序对数 ( R e v e r s e _ o r d e r _ p a i r \tt Reverse\_order\_pair Reverse_order_pair)
  1. 寻找伪币 ( C o u n t e r f e i t _ c u r r e n c y \tt Counterfeit\_currency Counterfeit_currency)
  1. 查找最接近的元素
  1. 一元三次方程求解
  1. 网线主管
  1. 膨胀的木棍 ( S t i c k \tt Stick Stick)

快速排序 & 二路归并排序

快速排序二路归并排序
不稳定排序稳定排序
双指针 & 二分二分

快速排序:

void quickly_sort(int s, int e) {
	if(s > e) {
		return;
	}
	int tmp = a[s];
	int i = s;
	int j = e;
	while(i != j) {
		while(a[j] <= tmp && i < j) {
			j --;
		}
		while(a[i] >= tmp && i < j) {
			i ++;
		}
		if(i < j) {
			swap(a[i], a[j]);
		}
	}
	swap(a[i], a[s]);
	quickly_sort(s, i - 1);
	quivkly_sort(i + 1, e);
	return;
}

二路归并:

void merge_sort(int s, int e) {
	if(s == e) {
		return;
	}
	int mid = (s + e) / 2;
	merge_sort(s, mid);
	merge_sort(mid + 1, e);
	int i = s;
	int k = s;
	int j = mid + 1;
	while(i <= mid && j <= e) {
		if(a[i] <= a[j]) {
			r[k] = a[i];
			k ++;
			i ++;
		}
		else {
			r[k] = a[j];
			k ++;
			j ++;
		}
	}
	while(i <= mid) {
		r[k] = a[i];
		i ++;
		k ++;
	}
	while(j <= e) {
		r[k] = a[j];
		j ++;
		k ++;
	}
	for(int i = s; i <= e; i ++) {
		a[i] = r[i];
	}
	return;
}

时间复杂度为 O ( n log ⁡ n ) \tt O(n\log n) O(nlogn)


求逆序对

看起来求逆序对又是大费周章,但其实在二路归并排序时,有一段代码:

else {
	r[k] = a[j];
	k ++;
	j ++;
}

先看 e l s e \tt else else 否定的是前面的 a [ i ] < = a [ j ] \tt a[i] <= a[j] a[i]<=a[j] , 则:

if(a[i] > a[j]) {
	r[k] = a[j];
	k ++;
	j ++;
}

再看 w h i l e \tt while while 里写的是 i < = m i d   & &   j < = e \tt i <= mid \ \&\& \ j <= e i<=mid && j<=e , 又因为 m i d = s + e 2 \tt mid = \frac {s + e}{2} mid=2s+e s < e \tt s < e s<e, 则 m i d < e \tt mid < e mid<e , 则 i < j \tt i < j i<j

满足逆序对的定义。

又想,既然 a [ i ] > a [ j ] \tt a[i] > a[j] a[i]>a[j], 则 a [ j ] \tt a[j] a[j]后的所有数均可和其互为逆序对。

则有:

else {
	r[k] = a[j];
	k ++;
	j ++;
	ans += mid - i + 1;//求逆序对
}

不能用快速排序求逆序对。

快速排序为不稳定排序,会导致相等元素下标交换。


寻找伪币

二分裸题。

想象一把天平,称重时伪币一定在轻的那一边。

则确定二分左指针 l = 1 \tt l = 1 l=1, 右指针 r = 16 \tt r = 16 r=16

m i d = s + e 2 \tt mid = \frac {s + e}{2} mid=2s+e

则当 ∑ i = l m i d a i < ∑ i = m i d + 1 r a i \tt \sum_{i = l}^{mid}a_i < \sum_{i = mid + 1}^{r}a_i i=lmidai<i=mid+1rai 时,伪币在 [ l , m i d ] \tt [l, mid] [l,mid] 中。

反之,伪币在 [ m i d + 1 , r ] \tt [mid + 1, r] [mid+1,r] 中。

为了降低时间复杂度,故采用前缀和 ( P r e a f i x   s u m ) \tt (Preafix \ sum) (Preafix sum) 数组。

int main() {
	for(int i = 1; i <= 16; i ++) {
		scanf("%d", &a[i]);
		prev[i] = prev[i - 1] + a[i];
	}
	int l = 1;
	int r = 16;
	while(l < r) {
		int mid = (l + r)  /2;
		int suml = prev[mid] - prev[l - 1];
		int sumr = prev[r] - prev[mid];
		if(suml < sumr) {
			r = mid;
		}
		else {
			l = mid + 1;
		}
	}
	printf("%d %d", l, a[l]);
	return 0;
}

查找最接近的元素

普通的二分查找。

值得注意的是:

所以在 w h i l e \tt while while 循环后, 还要进行一次判断:

if(x - a[l] <= a[r] - x) {//x是询问的数(查找对象)
	printf("%d\n", a[l]);
}

什么意思?

∣ a [ l ] − x ∣ < ∣ a [ r ] − x ∣ \tt | a[l] - x | < | a[r] - x | a[l]x<a[r]x 时, 输出 a [ l ] \tt a[l] a[l]

否则输出 a [ r ] \tt a[r] a[r]


一元三次方程求解

N O I P   2001 \tt NOIP \ 2001 NOIP 2001 提高组。

这种二分非直接分出答案,而是通过某些运算间接分出答案。

而我们往往将这某种运算写成函数,取名为 c h e c k \tt check check

int check(int x)

回到这道题。

首先导入一个概念:

零点定理

这就好办了。

double check(double x) {
	return x * x * x * a + b * x * x + c * x + d;
}
int main() {
	scanf("%lf %lf %lf %lf", &a, &b, &c, &d);
	for(double x = -100; x < 100; x += 1.0) {
		l = x;
		r = x + 1;
		if(check(l) == 0) {
			printf("%.2lf ", l);
		}
		else if(check(l) * check(r) < 0) {
			while(r - l > 0.0001) {
				mid = (r + l) / 2;
				if((check(l) * check(mid)) < 0) {
					r = mid;
				}
				else l = mid;
			}
			printf("%.2lf ", l);
		}
	}
	return 0;
}

网线主管

题目很长, 简单说就是:

仍然是间接二分。

则首先确定 c h e c k \tt check check 函数的内容。

二分分出来的是单位长度,而要与线段数量相比,很明显, 用一层 f o r \tt for for 循环累加每条线段按单位长度分得的线段数量。

int check(int x) {
	int ans = 0;
	for(int i = 1; i <= n; i ++) {
		ans += a[i] / x;
	} 
	return ans;
}

注意:为避免精度问题,将 d o u b l e \tt double double 转化为 i n t \tt int int 后处理,输出时将 i n t \tt int int 还原回 d o u b l e \tt double double 即可。


膨胀的木棍

热力学 & 三角学 & 高等数学联袂出演。

前方高能。

先热个身:

线性膨胀系数

l t = l 0 ( 1 + α t ) \tt l_t = l_0( 1 + \alpha t) lt=l0(1+αt)

又∵ ∠ α = arcsin ⁡ ( l r ) \tt ∠\alpha = \arcsin(\frac{l}{r}) α=arcsin(rl)

( 1 + α t ) l = 2 r × arcsin ⁡ ( l 2 r ) \tt (1 + \alpha t)l = 2r \times \arcsin(\frac{l}{2r}) (1+αt)l=2r×arcsin(2rl)

r = k 2 + l 2 \tt r = \sqrt{k^2 + l^2} r=k2+l2

又∵ d = r − k \tt d = r - k d=rk

d = k 2 + l 2 − k \tt d = \sqrt{k^2 + l^2} - k d=k2+l2 k

… \dots

最终得

r = h 2 + l 2 8 h \tt r = \frac{h}{2} + \frac{l^2}{8h} r=2h+8hl2

设弧长 x \tt x x

x = ( 2 h + l 2 2 h ) × arcsin ⁡ ( 2 h 4 h 2 + l 2 ) \tt x = (2h + \frac{l^2}{2h}) \times \arcsin(\frac{2h}{4h^2+l^2}) x=(2h+2hl2)×arcsin(4h2+l22h)

对上式求导得

x ′ = ( ( 2 h + l 2 2 h ) × arcsin ⁡ ( 2 h 4 h 2 + l 2 ) ) ′ \tt x\prime = ((2h + \frac{l^2}{2h}) \times \arcsin(\frac{2h}{4h^2+l^2}))\prime x=((2h+2hl2)×arcsin(4h2+l22h))

由求导四则运算法则与性质得

( u ( x ) × v ( x ) ) ′ = u ′ ( x ) × v ( x ) + u ( x ) × v ′ ( x ) \tt (u(x) \times v(x))\prime = u\prime(x) \times v(x) + u(x) \times v\prime(x) (u(x)×v(x))=u(x)×v(x)+u(x)×v(x)

( u ( x ) ± v ( x ) ) ′ = u ′ ( x ) + v ′ ( x ) \tt (u(x)±v(x))\prime = u\prime(x) + v\prime(x) (u(x)±v(x))=u(x)+v(x)

( u ( x ) v ( x ) ) ′ = u ′ ( x ) v ( x ) − u ( x ) v ′ ( x ) v 2 ( x ) \tt (\frac{u(x)}{v(x)})\prime = \frac{u\prime(x)v(x) - u(x)v\prime(x)}{v^2(x)} (v(x)u(x))=v2(x)u(x)v(x)u(x)v(x)

( ( 2 h + l 2 2 h ) × arcsin ⁡ ( 2 h 4 h 2 + l 2 ) ) ′ = ( 2 h + l 2 2 h ) ′ × arcsin ⁡ ( 2 h 4 h 2 + l 2 ) + ( 2 h + l 2 2 h ) × arcsin ⁡ ( 2 h 4 h 2 + l 2 ) ′ \tt ((2h + \frac{l^2}{2h}) \times \arcsin(\frac{2h}{4h^2+l^2}))\prime = (2h + \frac{l^2}{2h})\prime \times \arcsin(\frac{2h}{4h^2+l^2}) + (2h + \frac{l^2}{2h})\times \arcsin(\frac{2h}{4h^2+l^2})\prime ((2h+2hl2)×arcsin(4h2+l22h))=(2h+2hl2)×arcsin(4h2+l22h)+(2h+2hl2)×arcsin(4h2+l22h)

化简得

x ′ = 4 h + 2 h l + l 2 2 h × arcsin ⁡ ( 2 h 4 h 2 + l 2 ) + 4 h 2 + l 2 2 h 1 − 16 h 4 − 8 h 2 l 2 − l 4 \tt x\prime = \frac{4h + 2hl + l^2}{2h} \times \arcsin(\frac{2h}{4h^2 + l^2}) + \frac{4h^2+l^2}{2h\sqrt{1 - 16h^4 - 8h^2l^2 - l^4}} x=2h4h+2hl+l2×arcsin(4h2+l22h)+2h116h48h2l2l4 4h2+l2

x \tt x x 单调递增。

注意:二分时精度需开大。

int main() {  
    double len, n, c;    
    while(scanf("%lf%lf%lf", &len, &n, &c)) {  
        if (len == -1 && n == -1 && c == -1) {
            return 0;  
        }
        double l = 0.0;  
        double r = 0.5 * len;  
        double mid;  
        double l1 = (1 + n * c) * len;  
        while(r - l > 0.00001) {  
            mid = (l + r) / 2;  
            r = (4 * mid * mid + len * len) / (8 * mid);  
            if (2 * r * asin(len / (2 * r)) < l1) {
                l = mid;  
            }
            else {
                r = mid;  
            }
        }  
        printf("%.3lf\n", mid);
    }  
}

2.STL之队列

    Q   U   E   U   E \tt \ \ \ Q \ U \ E\ U \ E    Q U E U E

长这样☟

队列满足FIFO。[1]


用数组模拟队列

双指针法。

定义两个指针: f r o n t \tt front front r e a r \tt rear rear

int front;
int rear;

p u s h ( ) \tt push() push()

把元素装进队列。

把队列想成自动铅笔, p u s h ( ) \tt push() push()就是往笔里塞笔芯。

只需要将 r e a r \tt rear rear ++ 即可。

void push(int x) {
	rear ++;
	a[rear] = x;
	return;
}

e m p t y ( ) \tt empty() empty()

队列是否为空。

通俗的说就是铅笔里还有没有笔芯。

r e a r = f r o n t \tt rear = front rear=front 时,队列为空。

bool empty() {
	return rear == front;
}

f r o n t ( ) \tt front() front()

获取队头元素值(不删除)。

输出 a [ f r o n t + 1 ] \tt a[front + 1] a[front+1] 即可。

注意:队列为空时无法获取队头元素值。

void front() {
	if(empty()) {
		printf("error");
		return;
	}
	printf("%d" a[front + 1]);
	return;
}

p o p ( ) \tt pop() pop()

删除队头元素。

f r o n t \tt front front ++ 即可。

注意:队列为空时无法删除。

void pop() {
	if(empty()) {
		printf("error");
		return;
	}
	front ++;
	return;
}

c l e a n ( ) \tt clean() clean()

清空队列。

r e a r = f r o n t \tt rear = front rear=front 即可。

void clean() {
	rear = front;
	return;
}

STL里的队列

#include <cstdio>
#include <queue>
using namespace std;
queue<int> q;
int main() {
	q.clean();
	q.push(1);
	printf("%d", q.front());
	q.pop();
	printf("%d", q.empty());
	return 0;
}

输出:

1 1

周末舞会

创建两个队列,一个装男士,一个装女士。

int main() {
	queue<int> q1;
	queue<int> q2;
	int n, m, k;
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1; i <= n; i ++) {
		q1.push(i);
	}
	for(int i = 1; i <= m; i ++) {
		q2.push(i);
	}
	for(int i = 1; i <= k; i ++) {
		printf("%d %d\n", q1.front(), q2.front());
		q1.push(q1.front());
		q1.pop();
		q2.push(q2.front());
		q2.pop();
	}
}

Blah数集

现在小高斯想知道如果将集合 Blah 中元素按照升序排列,第 n 个元素会是多少?注意:集合中没有重复的元素。

开两个队列。

int main() {
	queue<int> q1;
	queue<int> q2;
	int ans, n;
	scanf("%d%d", &ans, &n);
	int tot = 1;
	while(tot != n) {
		q1.push(2 * ans + 1);
		q2.push(3 * ans + 1);
		if(q1.front() < q2.front()) {
			ans = q1.front();
			q1.pop();
		}
		else if(q1.front() > q2.front()) {
			ans = q2.front();
			q2.pop();
		}
		else {
			ans = q1.front();
			q1.pop();
			q2.pop();
		}
		tot ++;
	}
	printf("%d", ans);
	return 0;
}

3.STL之栈

    S   T   A   C   K \tt \ \ \ S \ T \ A \ C \ K    S T A C K

长这样☟

就像这摞书,如果你想要拿到最底下那本,你一定需要从上往下一本本拿出来才行。(技术好的也可以釜底抽薪)(大雾)


用数组模拟栈

单指针法。

定义一个指针 r e a r \tt rear rear

int rear;

p u s h ( ) \tt push() push()

将元素放进栈。

r e a r \tt rear rear ++ 并将 x \tt x x 赋值给 a [ r e a r ] \tt a[rear] a[rear] 即可。

void push(int x) {
	rear ++;
	a[rear] = x;
	return;
}

e m p t y ( ) \tt empty() empty()

栈是否为空。

判断 r e a r \tt rear rear 是否为 0 \tt 0 0 即可。

bool empty() {
	return rear == 0;
}

t o p ( ) \tt top() top()

获取栈顶元素值(不删除)。

输出 a [ r e a r ] \tt a[rear] a[rear] 即可。

注意:栈为空时无法获取栈顶元素值。

void top() {
	if(empty()) {
		printf("error");
		return;
	}
	printf("%d", a[rear]);
	return;
}

p o p ( ) \tt pop() pop()

删除栈顶元素。

r e a r \tt rear rear - - 即可。

void pop() {
	rear --;
	return;
}

c l e a n ( ) \tt clean() clean()

将栈置空。

r e a r \tt rear rear 置为 0 \tt 0 0 即可。

void clean() {
	rear = 0;
	return;
}

STL中的栈

#include <cstdio>
#include <stack>
using namespace std;
stack<int> s;
int main() {
	s.clean();
	s.push(1);
	printf("%d", q.top());
	printf("%d", q.empty());
	q.pop();
	printf("%d", q.empty());
	return 0;
}

输出:

1 0 1

   T   H   E E   N   D \tt \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \ \ T \ H \ E \quad E \ N \ D   T H EE N D

\quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad 新手的第一篇博客,请多多指教。


鸣谢: 重庆八中宏帆初级中学编程社

举报

相关推荐

0 条评论