看板娘加载较慢请耐心等待qwq~~~

ITMO 2022 winter camp

Basic

c++优化

c++会减少相同函数的重复调用

int f(int x)
{
    return f(x - 1) + f(x - 1); 
}

实际操作与以下写法相同

int f(int x)
{
    int c = f(x - 1);
    return c + c;
}

关闭输入输出同步

ios::sync_with_stdio(false);

解除cin/cout绑定

cin.tie(0);

以上写法会导致与scanf不同步,cin/cout不同步,在处理问答题的时候慎用。

debug

可以使用以下写法计算程序运行时间

double t = clock();
cout << (clock() - t) / CLOCKS_PER_SEC << endl;

可以使用

cerr //输出流
assert();//错误判断,值为false时会弹出错误

sort

挂一个自己觉得不错的博客 详解八大排序
题解快速排序

insert sort

算法思想:对每一个数,插入到已排好的数据中去。

复杂度 O(n^2)

for(register int i = 2; i <= n; ++i)
{
	for(register int j = i - 1; j >= 1; --j)
	{
		if(a[j] > a[j + 1])
			swap(a[j], a[j + 1]);
		else
			break;
	}
}

merge sort

算法思想:分治。将数列分为左右两侧,递归拆分,再按顺序合并。

复杂度O(nlogn),且稳定为O(nlogn)。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
int a[MAXN], b[MAXN], n, p = 1;
void merge(int arr[], int l, int mid, int r)
{
	int pl = l, pr = mid;
	int ql = mid + 1, qr = r;
	while(pl <= pr || ql <= qr)
	{
		if((ql > qr) || (pl <= pr && arr[pl] <= arr[ql]))
			b[p++] = arr[pl++];
		else
			b[p++] = arr[ql++];
	}
	while(l <= r)
		arr[r--] = b[--p];
}
void mergesort(int arr[], int l, int r)
{
	if(l >= r)
		return;
	int mid = (l + r) >> 1;
	mergesort(arr, l, mid);
	mergesort(arr, mid + 1, r);
	merge(arr, l, mid, r);
}
int main()
{
	cin >> n;
	for(register int i = 1; i <= n; ++i)
	{
		cin >> a[i];
	}
	mergesort(a, 1, n);
	for(register int i = 1; i <= n; ++i)
		cout << a[i] << " ";
}

逆序对

很简单,在合并的时候,如果右侧数列的数大,那么此时对答案的贡献就是左侧数列中未合并的数的个数。

只需要在merge里加一句话

ans +=  mid - pl + 1;

这个时候的merge是这样的

void merge(int arr[], int l, int mid, int r)
{
	int pl = l, pr = mid;
	int ql = mid + 1, qr = r;
	while(pl <= pr || ql <= qr)
	{
		if((ql > qr) || (pl <= pr && arr[pl] <= arr[ql]))
		{
			b[p++] = arr[pl++];
		}
		else
		{
			b[p++] = arr[ql++];
			ans +=  mid - pl + 1;
		} 
	}
	while(l <= r)
		arr[r--] = b[--p];
}

counting sort

思想:记录每个数出现了几次,从小到大输出。

复杂度:O(n + m)

while(scanf("%d", &x) != EOF)
	++cnt[x];
for(register int i = 0; i <= 100; ++i)
{
	for(register int j = 1; j <= cnt[i]; ++j)
	{
		cout << i << " ";
	}
}

其他情况

字母:c - 'a'

负数:x + m

大数据:离散化。

数对:另外开p数组,记录.first 为 i 的数对有几个,再分别按照.second排序

Binary Search

lower_bound & upper_bound

实现方法很多,这里记一个好写好用的

int lower_bound(int l, int r, int x)
{
	while(l <= r)
	{
		int mid = (l + r) >> 1;
		if(a[mid] < x)
			l = mid + 1;
		else
			r = mid - 1;
	}
	return l;
}
int upper_bound(int l, int r, int x)
{
	while(l <= r)
	{
		int mid = (l + r) >> 1;
		if(a[mid] <= x)
			l = mid + 1;
		else
			r = mid - 1;
	}
	return l;
}

调用时l,r分别为区间下端点和上端点。

查询数的个数

upper_bound(x) - lower_bound(x) 就可以得到区间内有几个x

lower_bound() & upper_bound() 函数

lower_bound
upper_bound

binary search on real numbers

while(r - l > ESP)
{
    if(f(mid) < x)
        l = mid;
    else
        r = mid;
}

但是由于浮点型精度损失,这边建议使用for限制循环次数,而不是使用while

for(register int i = 1; i <= 5000000; ++i)
{
	mid = (l + r) / 2;
	if(f(mid) < c)
		l = mid;
	else
		r = mid;
}

binary search by answer

二分答案是一个十分重要的解题思路,他需要满足与答案相关的check是在区间上单调可以二分。

\(check() = 0 (x < ans),check() = 1 (x >= ans)\)

这样题目就被简化成考虑如何在有限时间复杂度内完成check操作。

while(l <= r)
{
	int mid = (l + r) / 2;
	if(check(mid))
		r = mid - 1;
	else
		l = mid + 1;
}

Dynamic Programming

"backword"calculation & "forward"calculation

题目:从i转移到i+1花费b,从i转移到i+2花费c,收益为ai,最大化收益。

backword:

f[i] = max(f[i-1] + a[i] - b, f[i-2] + a[i] - c);

forward:

f[i+1] = max(f[i+1], f[i] + a[i+1] - b);

f[i+2] = max(f[i+2], f[i] + a[i+2] - c);

注意处理好边界

Longest increasing subsequence

f[i] = 1;
f[i] = max(f[i], f[j] + 1); (j < i && a[j] < a[i])

for(int i = 1; i <= n; ++i)
	f[i] = 1;
for(int i = 1; i <= n; ++i)
{
	for(int j = 1; j < i; ++j)
	{
		if(a[j] < a[i])
			f[i] = max(f[i], f[j] + 1);
	}
}

如何记录答案?

store the answer

直接开i个数组,每次转移成功后在g[i]末尾加上a[i];
时间复杂度O(n^3);

store the transition

记录转移,每次转移成功后g[i] = j;

提取答案可以从末尾向前跳转移

//记录答案并记录最末端是哪里
for(register int i = 1; i <= n; ++i)
{
	if(f[i] > ans)
	{
		ans = f[i];
		p = i;
	}
}
vector<int> A;
A.push_back(p);
//依次向前跳转移
while(p > 0)
{
	A.push_back(g[p]);
	p = g[p];
}

O(n)

calc the transition

不记录转移,但是在输出方案时计算可行转移。

while(p>0)
{
	ans = <a[p]> + ans;
	int b = 0;
	//找上一个转移
	for(int j = 1; j < p; ++j)
	{
		if(a[j] < a[p] && f[p] == f[j] + 1)
		{
			b = j;
			break;
		}
	}
	p = b;
}

O(n^2)

Non-universal way

使用这种方法需要保证转移的唯一性,不唯一的话是会错的。

while(p>0)
{
	for(int j = p-1; j >= 1; --j)
	{
		if(f[p] == f[j] + 1)
		{
			b = j;
			break;
		}
	}
	p = b;
}

edit distance problem

题目:string s,t。每次花费a在s中增加字符,花费d在s中删去字符,花费r在s中替换字符。求把s变为t的最小花费。

解:f[i][j]表示把s[1-i]变为t[1-j]的最小花费。

转移:

f[i][j] = f[i - 1][j - 1] (s[i] == t[j]);字符相等没有花费

f[i][j] = min(f[i][j], f[i - 1][j] + a);在s[1 - i-1]的基础上加一个字符

f[i][j] = min(f[i][j], f[i][j - 1] + d);在原先序列上减一个字符

f[i][j] = min(f[i][j], f[i - 1][j - 1] + r);在原先序列上替换一个字符

//题目里花费都是1
for(register int i = 0; i <= n; ++i)
{
	for(register int j = 0; j <= m; ++j)
	{
		if(i == 0 && j == 0)
		{
			f[i][j] = 0;
		}
		else
		{
			f[i][j] = n + m + 1;
			if(i > 0)
			{
				f[i][j] = min(f[i][j], f[i - 1][j] + 1);
			}
			if(j > 0)
			{
				f[i][j] = min(f[i][j], f[i][j - 1] + 1);
			}
			if(i > 0 && j > 0)
			{
				f[i][j] = min(f[i][j], f[i - 1][j - 1] + (s[i - 1] == t[j - ] ? 0 : 1));
			}
		}
	}
}

restoring

在f[i][j]图表中会显示出一个转移路径,从f[n][m] -> f[1][1] 的递减路径。

knapsack Problem

背包十分熟悉了,上代码。记录路径就开一个一样大的数组就好。

for(register int i = 1; i <= n; ++i)
{
	for(register int j = m; j >= v[i]; --j)
	{
		if(f[j] < f[j - v[i]] + c[i])
		{
			f[j] = f[j - v[i]] + c[i];
			vi[i][j] = true;
		}
		else
			vi[i][j] = false;
	}
}
for(register int i = 0; i <= m; ++i)
{
	if(f[i] > aaa)
	{
		aaa = f[i];
		M = i;
	}
}
for(register int i = n; i >= 1; --i)
{
	if(vi[i][M])
	{
		ans.push_back(i);
		M -= v[i];
	}
}
posted @ 2022-01-12 15:28  椎名·六花  阅读(53)  评论(0编辑  收藏  举报