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

例题 1 错排问题

fi 表示前 i 个数的错排。易得递推式为 fi=(i1)×(fi1+fi2)

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 传球游戏

fi,j 表示经过 j 次传到第 i 个人的方案数。则 fi,j=fi1,j1+fi+1,j1。注意 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 数的划分

fi,j 表示把 i 分成 j 份方案数。
分情况讨论:

  1. 若方案中不含 1,则可以由每个数都减一的情况转移得到,方案数为 fij,j
  2. 若含有 1,则方案数为 fi1,j1

故总转移方程为 fi,j=fij,j+fi1,j1

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.划分数列

fi,gi 分别表示划分到第 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×n 的方阵里填 1,且每行每列都保证只有一个 1,则每一种填数方式对应着一种排列。(不过讲真这个神仙转换方法我没想到/wq
从左上角开始一圈圈填数,转移过程分类讨论。
aiai1<0aiai1>2 时无解。
0,这一圈不用填,即 fi=fi1
1,这一圈有 2i1 个位置,其中横行或竖行被以前填的数占用的有 2×ai1 个。故 fi=fi1×(2i12×ai1)
2,只能在横行填一个竖行填一个,fi=fi1×(i1ai1)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(n3) 的复杂度怎么跑过的,大概是玄学吧(
枚举两个点暴力判断即可。

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 @   樱雪喵  阅读(182)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
历史上的今天:
2021-08-12 8.12 dp模拟赛总结
点击右上角即可分享
微信分享提示