算法整理(二)

前面的因为超字数发不了了,所以只好分成几篇发。。。

4. 搜索

4.1 DFS

4.1.1 图的深度优先遍历

编写程序对给定的有向图(不一定连通)进行深度优先遍历,图中包含n个顶点,编号为0至n-1。本题限定在深度优先遍历过程中,如果同时出现多个待访问的顶点,则优先选择编号最小的一个进行访问,以顶点0为遍历起点。

#include<bits/stdc++.h>
using namespace std;
vector<int>t[20010];
int visited[20010];

void dfs(int p)
{
	if(visited[p])
	return;
	visited[p]=1;
	cout<<p<<' ';
	for(int i=0;i<t[p].size();i++)
	{
		if(visited[t[p][i]])
		continue;
		dfs(t[p][i]);
	}
}

int main()
{
	int n,e,i,j;
	cin>>n>>e;
	while(e--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		t[a].push_back(b);
	}
	for(i=0;i<n;i++)
	{
		for(int p=0;p<t[i].size();p++)
		{
			int temp=p;
			for(j=p+1;j<t[i].size();j++)
			{
				if(t[i][j]<t[i][temp]) 
				temp=j;
			}
			swap(t[i][p],t[i][temp]);
		}
	}
	for(i=0;i<n;i++)
	dfs(i);
	return 0;
}

4.1.2 排列数和组合数

排列数
按照字典序输出n的全排列。

#include<bits/stdc++.h>
using namespace std;
int vis[12],a[12],n;

void dfs(int cnt)
{
	if(cnt==n)
	{
		for(int i=0;i<n;i++)
			printf("%d ",a[i]);
		printf("\n");
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			vis[i]=1;
			a[cnt]=i;
			dfs(cnt+1);
			vis[i]=0;
		}
	}
	return;
}

int main()
{
	cin>>n;
	dfs(0);
	return 0;
}

组合数

#include<bits/stdc++.h>
using namespace std;

int n,m;
vector<int>vec;

void dfs(int k)
{
    if(vec.size()>m||vec.size()+(n-k+1)<m) return;
    //到达枚举边界,输出结果并结束。可以剪枝。
    if(k==n+1)
    {
        for(int i=0;i<vec.size();i++)
            cout<<vec[i]<<" ";
        cout<<endl;
        return;
    }
    //选择这个数
    vec.push_back(k);
    dfs(k+1);
    //回溯
    vec.pop_back();
    //不选择这个数
    dfs(k+1);
}

int main()
{
    cin>>n>>m;
    dfs(1);
    return 0;
}


Gosper’s Hack是一种生成n元集合所有k元子集的算法。

void Gospers_Hack(int k, int n)
{
    int cur=(1<<k)-1;
    while(cur<1<<n)
    {
        int lowbit=cur&-cur;
        int high=cur+lowbit;
        cur=(((high^cur)>>2)/lowbit)|high;
    }
}
Gospers_Hack(3,5)
{
	cur=00111;
	while(cur<100000)
	{
		lowbit=00001;
		high=01000;
		high^cur=01111;
		(high^cur)>>2)=00011;
		(high^cur)>>2)/lowbit)=00011;(抹掉末尾0)
		cur=01011;
		......
	}
}

4.1.3 树的遍历

树的深度优先遍历
调用根结点 d f s ( r o o t ) dfs(root) dfs(root)

void dfs(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=nex[i])
	{
		int y=ver[i];
		if(vis[y]) continue;
		dfs(y);
	}
}

树的DFS序
对树进行深度优先遍历时,对于每个结点,在刚进入递归后以及即将回溯时各记录一次结点编号,每个结点x在序列中恰好出现两次。

void dfs(int x)
{
	a[++m]=x;
	vis[x]=1;
	for(int i=head[x];i;i=nex[i])
	{
		int y=ver[i];
		if(vis[y]) continue;
		dfs(y);
	}
	a[++m]=x;
}

树的深度

void dfs(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=nex[i])
	{
		int y=ver[i];
		if(vis[y]) continue;
		depth[y]=depth[x]+1;
		dfs(y);
	}
}

树的重心
有的时候题目不给明哪个是根结点(所有节点都可以是根结点),可以通过一次dfs找到树的重心来尽量避免“树大根深”的情况。
树的重心:设max_part(x)表示在删除结点x后产生的子树中,最大的一棵的大小。使得max_part取最小值的结点p即为树的重心。

void dfs(int x)
{
	vis[x]=1;size[x]=1;//子树x的大小 
	int max_part=0;
	for(int i=head[x];i;i=nex[i])
	{
		int y=ver[i]; 
		if(vis[y]) continue;
		dfs(y);
		size[x]+=size[y];
		max_part=max(max_part,size[y]);//找最大子树 
	}
	max_part=max(max_part,n-size[x]);
	if(max_part<ans)
	{
		ans=max_part;//记录重心对应的最大子树规模 
		pos=x;//记录重心 
	}
}

连通块划分

void dfs(int x)
{
	v[x]=cnt; 
	int max_part=0;
	for(int i=head[x];i;i=nex[i])
	{
		int y=ver[i]; 
		if(vis[y]) continue;
		dfs(y);
	}
}
int main()
{
	for(int i=1;i<=n;i++)
	{
		if(!v[i])
		{
			cnt++;
			dfs(i);
		}
	}
}

4.1.4 剪枝

剪枝

  1. 优化搜索顺序
  2. 排除冗余信息
  3. 可行性剪枝(照当前分支这样走后面不可能达成)
  4. 最优性剪枝(照当前分支这样走的最优解都比当前解差)
  5. 记忆化搜索

166. 数独

主要考虑优化搜索顺序,从可选的数少的空格开始试探。所以我们在每次搜索时都判断一下哪个格子的可选到数最小。

#include<bits/stdc++.h> 
using namespace std;
const int N=9;
string str;
int mp[1<<N],nums[1<<N],row[N],col[N],cell[N][N];

inline int lowbit(int x)
{
	return x&(-x);
}

int get(int x,int y)
{
	return row[x]&col[y]&cell[x/3][y/3];//求出这一个位置可能会被填哪些数字 
}

void init()//初始化:所有位所有数字都可以填 
{
	for(int i=0;i<N;i++) 
		row[i]=col[i]=(1<<N)-1;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			cell[i][j]=(1<<N)-1;
}

bool dfs(int cnt)
{
	if(cnt==0) return true;//所有待填数字都已经被填上 
	int minn=10,x,y;
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
		{
			if(str[i*9+j]=='.')
			{
				int temp=nums[get(i,j)];
				if(temp<minn)
				{
					minn=temp;
					x=i,y=j;
				}
			}
		}//找到所有剩下的待填格中数字集合最小的一个,是一个剪枝过程 
	for(int i=get(x,y);i;i-=lowbit(i))//枚举这一格所有可能填的数字 
	{
		int t=mp[lowbit(i)];
		row[x]-=(1<<t);
		col[y]-=(1<<t);
		cell[x/3][y/3]-=(1<<t);
		str[x*9+y]='1'+t;
		if(dfs(cnt-1)) return true;
		row[x]+=(1<<t);
		col[y]+=(1<<t);
		cell[x/3][y/3]+=(1<<t);
		str[x*9+y]='.';//复原 
	}
	return false;
}


int main()
{
	for(int i=0;i<N;i++) mp[1<<i]=i;//预先存下二进制1所在的位置 
	for(int i=1;i<(1<<N);i++)
		for(int j=i;j;j-=lowbit(j)) nums[i]++;
		//预先存下每个之后可能会枚举到的二进制数内有几个1 
	while(cin>>str&&str[0]!='e')
	{
		init();
		int cnt=0;
		for(int i=0,k=0;i<9;i++)
			for(int j=0;j<9;j++,k++)
			{
				if(str[k]!='.')
				{
					int t=str[k]-'1';//1~9映射到0~8 
					row[i]-=(1<<t);
					col[j]-=(1<<t);
					cell[i/3][j/3]-=(1<<t);
				}//这一位数字是题给的,把它存进对应位置 
				else cnt++;//待填数字+1 
			}
		dfs(cnt);
		cout<<str<<endl;
	}
	return 0;
}

4.1.5 迭代加深

搜索树规模随着层次的深入增长很快,并且能够确保答案在一个较浅层的结点内——>限制搜索深度,每次在该深度内找不到答案就返回,增加一单位深度后再搜索。这会导致重复搜索浅层,但这比起庞大的子树来微不足道。比如是二叉树, 1 + 2 + 2 2 + 2 3 + . . . + 2 d e p t h − 1 < 2 d e p t h 1+2+2^{2}+2^{3}+...+2^{depth-1}<2^{depth} 1+2+22+23+...+2depth1<2depth

170. 加成序列
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int path[N],st[N],n;

bool dfs(int u,int depth)
{
	if(u==depth) return path[u-1]==n;//最后一个数是n即找到答案 
	bool st[N]={0};
	for(int i=u-1;i>=0;i--)
		for(int j=i;j>=0;j--)//优化搜索顺序,i和j从大到小枚举
		{
			int temp=path[i]+path[j];
			if(!st[temp]&&temp<=n&&path[u-1]<temp)
			//!st[temp]剪枝,因为有多种可能的 temp=path[i]+path[j],但是只需要搜索一次
			//这个数一定<=n且比前一层的数大 
			{
				path[u]=temp;
				st[temp]=1;
				if(dfs(u+1,depth)) return true;
			}
		}
		
	return false;
}
int main()
{
	path[0]=1;
	while(cin>>n,n)
	{
		int depth=1;
		while(!dfs(1,depth)) depth++;//迭代加深的过程
		for(int i=0;i<depth;i++)
			cout<<path[i]<<' ';
		cout<<endl;
	}
	return 0; 
}


4.1.6 双向搜索

从初态和终态出发各搜索一半状态,产生两棵深度减半的搜索树,在中间交会、组合成最终的答案。

171. 送礼物
在这里插入图片描述

把礼物分成前一半,后一半,对于前一半,就暴搜,记录每次搜索完成后的总重量;对于后一半,也暴搜,每次搜索完成后与前一半合并。怎么合并?在前一半二分出一个重量,这个重量和后一半暴搜完的重量加起来不超过w而且尽可能大。用ans记录最大的一个合并总重。

#include<bits/stdc++.h>
#define llg long long
#define inf 0x3ffffff

using namespace std;

const int N=46;
llg weight[1<<24],ans,g[N],w,n,k,cnt,tot;

void dfs1(int v,int s)
{
	if(v==k)
	{
		weight[cnt++]=s;
		return;
	}
	if((llg)s+g[v]<=w) dfs1(v+1,s+g[v]);//选 
	dfs1(v+1,s);//不选 
}

void dfs2(int v,int s)
{
	if(v==n)
	{
		llg l=0,r=tot-1;
		while(l<r)
		{
			llg mid=(l+r+1)>>1;
			if(weight[mid]+(llg)s<=w) l=mid;
			else r=mid-1;
		}
		ans=max(ans,s+weight[l]);
		return;
	}
	if((llg)s+g[v]<=w) dfs2(v+1,s+g[v]);//选 
	dfs2(v+1,s);//不选 
}
int main()
{
	cin>>w>>n;
	for(int i=0;i<n;i++)
		scanf("%lld",&g[i]);
	sort(g,g+n);
	reverse(g,g+n);
	k=n/2;
	dfs1(0,0);
	sort(weight,weight+cnt);
	tot=unique(weight,weight+cnt)-weight;
	dfs2(k,0);
	cout<<ans;
}

4.2 BFS

求最长路

// 1:50ms /  3.21MB /  863B C++14 (GCC 9) O2
// 2:48ms /  3.13MB /  877B C++14 (GCC 9) O2
#include<bits/stdc++.h> 
using namespace std;
queue<int>q;
int n,m;
int maxn[1510],vis[1510];
//maxn:到当前结点之前的最长路 
 
struct Edge
{
	int from;
	int to;
	int w;
}edge[50010];
vector<Edge>vec[50010];//vec为Edge型的,在已知起点时可直接通过这个调用出边权和终点 
 
void SPFA1(int start)
{
	maxn[start]=0;
	q.push(start);
	while(q.size())
	{
		int now=q.front();//每次取出队首元素 
		q.pop();
		for(int j=0;j<vec[now].size();j++)//遍历队首元素指向的所有结点 
		{
			if(maxn[vec[now][j].to]<maxn[now]+vec[now][j].w)
			{
				maxn[vec[now][j].to]=maxn[now]+vec[now][j].w;
				//如果from之前的最长路加上边权大于之前获得的to之前的最长路,就更新一下 
				q.push(vec[now][j].to);
				//只有当需要更新的时候才需要将to压入队列,否则from的前驱中一定会有结点到 to的更长路径,而当时已经入队过了 
			} 
		}
	}
}
 
void SPFA2(int start)
{
	maxn[start]=0;
	vis[start]=1;
	q.push(start);
	while(q.size())
	{
		int now=q.front();
		q.pop();
		vis[now]=0;
		for(int i=0;i<vec[now].size();i++)
		{
			if(maxn[vec[now][i].to]<maxn[now]+vec[now][i].w)
			{
				maxn[vec[now][i].to]=maxn[now]+vec[now][i].w;
				if(!vis[vec[now][i].to])
				{
					vis[vec[now][i].to]=1;
					q.push(vec[now][i].to);
				}
			}
		}
	}
}
 
int main()
{
	int i;
	cin>>n>>m;
	for(i=0;i<m;i++)
	{
		scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].w);
		vec[edge[i].from].push_back(edge[i]);
	}
	memset(maxn,-1,sizeof(maxn));
	SPFA2(1);
	cout<<maxn[n];
	return 0;
}

5.模块化

5.1 快读

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

5.2 二分

lower_bound(vec.begin(),vec.end(),x)查找>=x的元素中最小的一个,并返回该元素的迭代器。
upper_bound(vec.begin(),vec.end(),x)查找>x的元素中最小的一个,并返回该元素的迭代器。

二分查找

#include<bits/stdc++.h>
using namespace std;
int a[1001],cnt=0,n;
int Binary(int a[],int x)
{
    int l=0,r=n-1,flag=0,mid;
    while(l<=r)
    {
        cnt++;
        int mid=(l+r)>>1;
        if(a[mid]<=x)
        {
            if(a[mid]==x) return mid;
            else l=mid+1;
        }
        else r=mid-1;
    }
    return -1;
}

int main()
{
    int x;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    cin>>x;
    sort(a,a+n);
    cout<<Binary(a,x)<<endl;
    cout<<cnt;
    return 0;
}

三分
给出一个 N 次函数,保证在范围 [l, r] 内存在一点 x,使得 [l, x]上单调增,[x, r]上单调减。试求出 xx 的值。

#include<bits/stdc++.h>
using namespace std;
int n;
double co[14],l,r;
double f(double x)
{
	double ans=0;
    for(int i=n;i>=0;i--) 
		ans=ans*x+co[i];
    return ans;
}
int main()
{
	cin>>n>>l>>r;
	for(int i=n;i>=0;i--)
		cin>>co[i];
	while(r-l>=1e-6)
	{
		double lmid=l+(r-l)/3.0;
		double rmid=r-(r-l)/3.0;
		if(f(lmid)<f(rmid)) l=lmid;
		else r=rmid;
	}
	cout<<l;
	return 0;
}

5.3 倍增

给定一个长度为 NN 的数列,和 MM 次询问,求出每一次询问的区间内数字的最大值。

#include<bits/stdc++.h>
using namespace std;
int n,m,f[100010][20];
 
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		f[i][0]=read();
	int t=log(n)/log(2);
	for(int j=1;j<=t;j++)
		for(int i=1;i<=n-(1<<j)+1;i++)
			//f[i][j]表示起始点为i,长度为2^{j}的区间
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	while(m--)
	{
		int r,l;
		l=read(),r=read();
		int k=log(r-l+1)/log(2);
		int ans=max(f[l][k],f[r-(1<<k)+1][k]);
		printf("%d\n",ans);
	}
	return 0;		
}

5.5 离散化

for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;i++)
	a[i]=lower_bound(b+1,b+n+1,a[i])-b;

6.字符串

6.1 字符串哈希

给定 N 个字符串(第 i个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N个字符串中共有多少个不同的字符串。

#include<bits/stdc++.h>
using namespace std;
map<int,bool>vis;//or set<int>setting;
int mod=1e9+7;
int base=13331;
int turn(char ch)
{
	if(ch>='0'&&ch<='9')
	return ch-'0'+1;
	if(ch>='A'&&ch<='Z')
	return ch-'A'+11;
	if(ch>='a'&&ch<='z')
	return ch-'a'+37;
}
 
int main()
{
	int N,cnt=0;
	cin>>N;
	while(N--)
	{
		string s;
		cin>>s;
		int ha=0;
		for(int i=0;i<s.size();i++)
		{
			ha=(1ll*ha*base%mod+turn(s[i]))%mod;
		}
		if(!vis[ha])//setting.insert(ha);
		cnt++;
		vis[ha]=1;
	}
	cout<<cnt;//cout<<setting.size();
	return 0;
}

给定一个字符串 text 和一个模式串 pattern,求 pattern 在text 中的出现次数。text 和 pattern 中的字符均为英语大写字母或小写字母。text中不同位置出现的pattern 可重叠。

#include<iostream> 
using namespace std;
string str,S;
int hashh[1000005],hashstr=0;
long long subhash=0;
int base=13331,mod=1e9+7;
int cnt=0;
 
int main()
{
	cin>>S>>str;
	int i,j;
	//cout<<"base "<<base<<endl;
	for(i=0;i<str.size();i++)
	{
		hashstr=(1ll*hashstr*base%mod+str[i]-'A'+1+mod)%mod;
		//一般而言需要-'A'+1,也就是'A'对应的hash值为1,如果'A'对应的hash值为0则容易冲突
	}
	int len=str.size();
	int p=1;
	for(i=1;i<=len;i++)
	{
		p=1ll*p*base%mod;
	}
	hashh[0]=(S[0]-'A'+1+mod)%mod;
	for(i=1;i<S.size();i++)
	{
		hashh[i]=(1ll*hashh[i-1]*base%mod+S[i]-'A'+1+mod)%mod;
		if(i>=len-1)
		{
			if(i==len-1)
			subhash=hashh[len-1];
			else if(i>len-1)
			{
				subhash=(mod+hashh[i]-1ll*hashh[i-len]*p%mod)%mod;
			}
			//cout<<"subhash "<<subhash<<endl;
			if(subhash==hashstr)
				cnt++;
			//cout<<cnt<<endl;
		}
	}
	cout<<cnt;
	return 0;
}

6.2 KMP

在这里插入图片描述

#include<bits/stdc++.h> 
using namespace std;
const int N=1000010;
char s1[N],s2[N];
int co[N],f[N];

int main()
{
	scanf("%s%s",s1+1,s2+1);
	int n=strlen(s1+1),m=strlen(s2+1);
	for(int i=2,j=0;i<=m;i++)
	{
		while(j>0&&s2[j+1]!=s2[i]) j=co[j];
		if(s2[j+1]==s2[i]) j++;
		co[i]=j;
	} 
	//ABABABC
	//ABA
	for(int i=1,j=0;i<=n;i++)
	{
		while(j>0&&(j==n||s2[j+1]!=s1[i])) j=co[j];
		if(s2[j+1]==s1[i]) j++;
		f[i]=j;
		if(f[i]==m) cout<<i-m+1<<endl;
	}
	for(int i=1;i<=m;i++)
		printf("%d ",co[i]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春弦_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值