noip知识点总结

基本算法

( 1 )位运算

基本运算
状压
运用:状压dp

求从0到n-1,每个点经过一次的最短路径

#include
using namespace std;
const int N = 20 + 1;
int f[1<

成对运算:

当n为偶数时 n xor 1 = n+1
当n为奇数时 n xor 1 = n-1
用于取邻接表的正反边。

(2)枚举,模拟,递推

前缀和 (差分数组)

设差分数组 d[i],= a[i] - a[i-1]; 则有
s u m 1 i d [ i ] = a [ i ] sum_{1}^{i}d[i] = a[i] sum1id[i]=a[i]
d[L] += c,d[R+1] -= c;
就可以得出a的l–r减去c的答案

(3)二分 : 务必使用配套的mid取法。

查找x的后继

while(l != r)
{
	int mid = (l + r)>>1;
	if(a[mid] >= x) r = mid;else l = mid+1;
}

查找x的前趋

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

查找x

 while(l != r)
{
	int mid = (l + r) >> 1;
	if(a[mid] == x)	ans = mid;
	else 
	{
		if(a[mid] > x) r = mid-1;else l = mid+1;
	}
}

二分答案

排序离散

	sort(b+1,b+1+n);
	m = unique(b+1,b+1+n)-b-1;
	for (int i = 1;i <= n;i++)
	  a[i] = lower_bound(b+1,b+1+m,a[i])-b;

(4) 倍增

RMQ

其他应用:

p = 1,k = 0,sum = 0;
while(p)
{
	if(sum + s[R+p] - s[L] <= T)
	sum += s[k+p],R += p,P <<= 1;
	else p >>= 1;
}

(5)贪心

(6)单调栈

	找到从左到右第一个比i大或小的值的位置;
	tac[]存下标,f[]存下标,tac中单调递增,
	f[]是从左到右第一个比i大或小的值的位置;
	int t = 0;
	for (int i = 1;i <= n;i++)
	{
		while(t && a[tak[t]] <= a[i]) --t;
		if(t) f[i] = tac[t];
		else f[i] = i;
		tak[++t] = i;
	}

单调队列

int l = 1,r = 1;
q[1] = 0;
for (int i = 1;i <= n;i++)
{
	while(l <= r && q[l] < i - m) l++;
	ans = max(ans,sum[i] - sum[q[l]]);
	while(l <= r && sum[q[r]] >= sum[i]) r--;
	q[++r] = i;
}

kmp

while(t1 < lent)
{
	if(t2 == -1 || t[t1] == t[t2]) nxt[++t1] = ++t2;
	else t2 = nxt[t2];
}

int i = 0,j = 0;
while(i < lens)
{
	if(j == -1 || s[i] == t[j])i++,j++;
	else j = nxt[j];
	if(j == lent){j = nxt[j];}  
}

一. 动态规划
最长上升子序列:

#include
using namespace std;
const int N = 1e3 + 10;
int f[N],n,a[N],ans;
int main()
{
	freopen("c.in","r",stdin);freopen("c.out","w",stdout);
	scanf("%d",&n);
	for (int i = 1;i <= n;i++)
	{
		scanf("%d",&a[i]);
	}

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

	for (int i = 1;i <= n;i++)
	{
		cout<

·变式「一」
题目描述
擦掉数列中某些数后,剩下的数列中在自己位置上的数尽量多。

输入输出格式
输入格式:
第一行为一个数n,表示数列的长度。

接下来一行为n个用空格隔开的正整数,第i行表示数Ai。

输出格式:
一行一个整数,表示擦掉某些数后剩下的数列中最多能有多少个数在自己的位置上
即Ai=i最多能有多少。

输入输出样例
输入样例 #1:

5
1 1 2 5 4
输出样例 #1:
3

说明
对于20%的数据,n≤20;
对于60%的数据,n≤100;
对于100%的数据,n≤l000。

最后在自己位置上的数字有一个特点:
即是递增的,因为标号递增。
但不完全是最长上升子序列。
两个数字间可以通过删数使他们到自己的位置上的一个充要条件是 a[i] - a[j] <= i-j
因为删数只能使它们之间的距离减小,如果原本就超过指定距离(i-j)则不能通过删数达成

由此得出方程 f [ i ] = m a x ( f [ j ] ) a [ i ] > a [ j ] , i > j , a [ i ] − a [ j ] < = i − j f[i] = max(f[j])_{a[i] > a[j] ,i > j, a[i]-a[j] <= i-j} f[i]=max(f[j])a[i]>a[j],i>j,a[i]a[j]<=ij

#include
using namespace std;
const int N = 1e3 + 10;
int f[N],n,a[N],ans;
int main()
{
	scanf("%d",&n);
	for (int i = 1;i <= n;i++) 	scanf("%d",&a[i]);
	for (int i = 1;i <= n;i++)
	{
		f[i] = 1;
		for (int j = 1;j < i;j++)
		if(a[i] > a[j] && a[i] - a[j] <= i-j)	f[i] = max(f[i],f[j] + 1);
		ans = max(ans,f[i]);
	}
	printf("%d\n",ans);
	return 0;
}
最长公共上升子序列

O ( n 3 ) O(n^3) On3

for (int i = 1;i <= n;i++)
for (int j = 1;j <= m;j++)
{
	if(a[i] == b[j])//满足公共
	{
		for (int k = 0; k < j;k++)
			if(b[k] < a[i])//满足上升
				f[i][j] = max(f[i][j],f[i-1][k] + 1);
	}
	else f[i][j] = f[i-1][j];
}

O ( n 2 ) O(n^2) O(n2)

考虑到最外层i是固定的,所以for k 0 -> j ,在j+1的时候,只有j+1,进入了决策集合,而集合是不减的,所以只需o(1)判断是否可以加入即可

for (int i = 1;i <= n;i++)
{
	int v = 0;
	if(b[0] < a[i]) v = f[i-1][0];//j == 1时,0可以作为k的取值。
	for (int j = 1;j <= m;j++)
	{
		if(a[i] == b[j])f[i][j] = v + 1;
		else f[i][j] = f[i-1][j];
		if(a[i] > b[j]) v = max(v,f[i-1][j]);
	}
}

你可能感兴趣的:(学习历程)