YbtOJ 「基础算法」第1章 递推算法

例题 1 错排问题

\(f_i\) 表示前 \(i\) 个数的错排。易得递推式为 \(f_i=(i-1)\times(f_{i-1}+f_{i-2})\)

code
#include<bits/stdc++.h>
#define int long long

using namespace std;
int n,f[25];
signed main()
{
	scanf("%lld",&n);
	f[1]=0,f[2]=1;
	for(int i=3;i<=n;i++) f[i]=(i-1)*(f[i-1]+f[i-2]);
	cout<<f[n]<<endl;
	return 0;
}

例题 2 传球游戏

\(f_{i,j}\) 表示经过 \(j\) 次传到第 \(i\) 个人的方案数。则 \(f_{i,j}=f_{i-1,j-1}+f_{i+1,j-1}\)。注意 \(j\) 要在外层循环。

code
#include<bits/stdc++.h>
using namespace std;
int n,m,f[35][35];
int main()
{
	scanf("%d%d",&n,&m);
	f[1][0]=1;
	for(int j=1;j<=m;j++)
	{
		for(int i=1;i<=n;i++)
		{
			int l=i-1,r=i+1;
			if(l<1) l=n;
			if(r>n) r=1;
			f[i][j]=f[l][j-1]+f[r][j-1];
		}
	}
	cout<<f[1][m]<<endl;
	return 0;
}

例题 3 数的划分

\(f_{i,j}\) 表示把 \(i\) 分成 \(j\) 份方案数。
分情况讨论:

  1. 若方案中不含 \(1\),则可以由每个数都减一的情况转移得到,方案数为 \(f_{i-j,j}\)
  2. 若含有 \(1\),则方案数为 \(f_{i-1,j-1}\)

故总转移方程为 \(f_{i,j}=f_{i-j,j}+f_{i-1,j-1}\)

code
#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[205][10];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++) 
		{
			if(i<=j) f[i][j]=(i==j);
			else f[i][j]=f[i-1][j-1]+f[i-j][j];
		}
	}
	cout<<f[n][m]<<endl;
	return 0;
}
//7 3

例题 4 栈的问题

设入栈为 \(+1\),出栈为 \(-1\),要求中间过程中和不得小于 \(0\)。则题目转化为卡特兰数问题,直接套公式即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,f[50];
signed main() 
{
	scanf("%lld",&n);
	f[0]=1;
	for(int i=1;i<=n;i++) f[i]=f[i-1]*(4*i-2)/(i+1);
	cout<<f[n]<<endl; 
	return 0;
}

1.划分数列

\(f_i,g_i\) 分别表示划分到第 \(i\) 位时,当前段为上升/下降时的最小段数。
转移方程见代码。(懒

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,a[N],f[N],g[N]; 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[1]=g[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(a[i]>a[i-1]) f[i]=min(f[i-1],g[i-1]+1),g[i]=min(g[i-1],f[i-1])+1;
		else if(a[i]==a[i-1]) f[i]=min(f[i-1],g[i-1]+1),g[i]=min(g[i-1],f[i-1]+1);
		else f[i]=min(f[i-1],g[i-1])+1,g[i]=min(g[i-1],f[i-1]+1);
	}
	cout<<min(f[n],g[n])<<endl;
	return 0;
}

2.求 f 函数

直接根据题意模拟时间开销过大,仿照记忆化搜索的思路,开数组存下已经求过的内容即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,f[N],ans;
int find(int x)
{
	if(f[x]) return f[x];
	if(x>=1001) return f[x]=x-10;
	else
	{
		return f[x]=find(find(x+11));
	}
}
int main()
{
	while(1)
	{
		scanf("%d",&n);
		if(n==0) break;
		//cout<<find(n)<<endl;
		ans^=find(n);
	}
	cout<<ans<<endl;
	return 0;
}

3.无限序列

手算该数列的前几次变化情况,可得每次变换得到的数列为前两次的数列拼接而成。
分别递推维护序列长度及对应 \(1\) 的数量。
\([a,b]\)\(1\) 的数量转化为前缀和相减的形式,不断二分减小 \(a,b\) 的值求出答案。
注意防止爆 long long。

code
#include<bits/stdc++.h>
#define int unsigned long long

using namespace std;
const int N=1e6+5;
int Q,a,b,n;
struct node{
	int len,cnt;
}f[N];
int sum(int x)
{
	int ans=0;
	while(x)
	{
		int l=1,r=n;
		if(f[n].len<=x) {x-=f[n].len;continue;}
		while(l<r)
		{
			int mid=(l+r+1)>>1;
			if(f[mid].len<=x) l=mid;
			else r=mid-1;
		}
		x-=f[l].len;ans+=f[l].cnt;
	}
	return ans;
}
signed main()
{
	scanf("%llu",&Q);
	f[1].len=f[1].cnt=f[2].cnt=1;
	f[2].len=2;
	for(int i=3;;i++)
	{
		f[i].len=f[i-1].len+f[i-2].len;
		f[i].cnt=f[i-1].cnt+f[i-2].cnt;
		if((int)pow(2,63)-f[i].cnt<=f[i-1].cnt) {n=i;break;} 
	}
	while(Q--)
	{
		scanf("%llu%llu",&a,&b);
		//maxn=max(a,b);
		cout<<sum(b)-sum(a-1)<<endl;
	}
	return 0;
}

4.序列个数

首先将题意转化为在 \(n\times n\) 的方阵里填 \(1\),且每行每列都保证只有一个 \(1\),则每一种填数方式对应着一种排列。(不过讲真这个神仙转换方法我没想到/wq
从左上角开始一圈圈填数,转移过程分类讨论。
\(a_i-a_{i-1}<0\)\(a_i-a_{i-1}>2\) 时无解。
\(0\),这一圈不用填,即 \(f_{i}=f_{i-1}\)
\(1\),这一圈有 \(2i-1\) 个位置,其中横行或竖行被以前填的数占用的有 \(2\times a_{i-1}\) 个。故 \(f_i=f_{i-1}\times (2i-1-2\times a_{i-1})\)
\(2\),只能在横行填一个竖行填一个,\(f_i=f_{i-1}\times (i-1-a_{i-1})^2\)
注意取模。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5;
const int mod=340610;
int n,a[N],f[N];
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	f[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(a[i]<a[i-1]) {cout<<"0"<<endl;return 0;}
		else if(a[i]==a[i-1]) f[i]=f[i-1];
		else if(a[i]-a[i-1]==1) f[i]=f[i-1]*(2*i-1-2*a[i-1])%mod; 
		else if(a[i]-a[i-1]==2) f[i]=f[i-1]*(i-1-a[i-1])%mod*(i-1-a[i-1])%mod;
		else {cout<<"0"<<endl;return 0;}
	}
	cout<<f[n]%mod<<endl;
	return 0;
}

5.等距跳跃

其实我也不知道 \(O(n^3)\) 的复杂度怎么跑过的,大概是玄学吧(
枚举两个点暴力判断即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int r,c,n,maxn;
int tot;
struct node{
	int x,y;
}a[N];
bool cmp(node e,node f){
	if(e.x==f.x) return e.y<f.y;
	else return e.x<f.x;
}
int mp[N][N];
int main()
{
	scanf("%d%d%d",&r,&c,&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i].x,&a[i].y);
		mp[a[i].x][a[i].y]++; 
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			int dux=2*a[i].x-a[j].x,duy=2*a[i].y-a[j].y;
			if(dux>=1&&dux<=r&&duy>=1&&duy<=c) continue; 
			int xx=a[i].x,yy=a[i].y;
			int dx=a[j].x-a[i].x,dy=a[j].y-a[i].y;
			int ans=1;
			while(xx<=r&&yy<=c)
			{
				xx+=dx,yy+=dy;
				if(xx>=1&&xx<=r&&yy>=1&&yy<=c&&(!mp[xx][yy])) {ans=0;break;}
				if(xx>=1&&xx<=r&&yy>=1&&yy<=c) ans++;
				else break;
			}
			//if(ans>=maxn) {cout<<ans<<" "<<a[i].x<<" "<<a[i].y<<" "<<a[j].x<<" "<<a[j].y<<endl;} 
			if(ans>=3) maxn=max(ans,maxn);	
		}
	}
	cout<<maxn<<endl;
	return 0;
}
posted @ 2022-08-12 21:48  樱雪喵  阅读(133)  评论(0编辑  收藏  举报