8月11日训练总结 HDU3068 POJ2449 HDU2234

本文总结了使用Manacher模板解决HDU3068最长回文问题,SPFA结合A*搜索求解POJ2449最短路径,并探讨了A*搜索在HDU2234问题中的应用,包括康托展开的概念及其在排列组合中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

manacher模板

HDU3068最长回文

就模板

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N=2e5+5;
char s0[2],s[N],ans[N<<1];//ans加工后字符串 
int res[N<<1],maxn;//回文串长度字符串 
int add(char s[])
{
	int l=strlen(s);
	ans[0]='@';
	for(int i=1;i<=(l<<1);i+=2)
	{
		ans[i]='#';
		ans[i+1]=s[i/2];
	}
	ans[(l<<1)+1]='#';
	ans[(l<<1)+2]='$';
	ans[(l<<1)+3]='\0';
	return (l<<1)+1;
}
int manacher(char s[],int l)
{
	int mx=0,po=0,p;
	maxn=0;
	for(int i=1;i<=l;i++)
	{
		if(mx>i)
			res[i]=min(mx-i,res[(po<<1)-i]);
		else
			res[i]=1;
		while(s[i-res[i]]==s[i+res[i]])
			res[i]++;
		if(mx<res[i]+i)
		{
			po=i;
			mx=res[i]+i;
		}
		if(res[i]>maxn)
		{
			maxn=res[i];
			p=i;
		}
	}
	return maxn-1;//maxn-1是原串最长回文串长度,p/2-1是maxn-1为奇数时的中心位置 ,偶数时的左中心 
}
int main()
{
	while(~scanf("%s",s))
		printf("%d\n",manacher(ans,add(s)));
}

SPFA+A*搜索

POJ2449Remmarguts' Date

求第k短路。这个题是可以经过终点的。不过终点的话,BFS 到终点直接continue即可。

F=G+H

先用spfa求出反向最短路,即终点到各个点的距离。作为H,然后开始BFS,G为起点走到该点的距离,不一定是起点到该点的最短距离,放进优先队列,G+H即为从起点以该点为路径上一个点到终点的距离。到达一次终点记录一下。

1.一开始以为双向边,看一下样例啊大哥

2.A*算法的使用技巧是将问题转换为图论问题再进行搜索,因为通过这样可以使估值函数与距离有关,更好被设定出来

 

#include <queue>
#include <cstdio>
#include <vector> 
#include <cstring>
#include <iostream>
using namespace std;
const int N=1010;
const int INF=0x3f3f3f3f;
struct Edge
{
	int to,w;
	Edge (int _to,int _w):to(_to),w(_w){}	
};
int h[N];
struct node
{
	int id,dis;
	node (int _id,int _dis):id(_id),dis(_dis){}
	bool operator < (const node &b) const //重载优先队列,路径短的放在前面
	{return dis+h[id]>b.dis+h[b.id];}
};
vector<Edge>tr11111[N],tr2[N];
void spfa(int t)//求反向最短路径
{
	queue<int>q;
	bool vis[N];
	memset(vis,0,sizeof(vis));
	memset(h,INF,sizeof(h));
	q.push(t);
	h[t]=0;
	vis[t]=1;
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		vis[tmp]=0;
		for(int i=0;i<tr2[tmp].size();i++)
		{
			int v=tr2[tmp][i].to;
			if(h[v]>h[tmp]+tr2[tmp][i].w)
			{
				h[v]=h[tmp]+tr2[tmp][i].w;
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}	
	}
}
int cnt[N],s,t,k;
inline int astar()
{
	if(h[s]==INF)//起点无法到达终点
		return -1;
	memset(cnt,0,sizeof(cnt));
	node tmp=node(s,0);
	priority_queue<node>q; 
	q.push(tmp);
	while(!q.empty())
	{
		tmp=q.top();
		q.pop();
		cnt[tmp.id]++;//记一下该点被到达了几次
		if(cnt[t]==k)
			return tmp.dis;
		if(cnt[tmp.id]>k)//一个点不可能经过多于k次,超过k了肯定不会再经过
			continue;
		for(int i=0;i<tr11111[tmp.id].size();i++)//BFS
		{
			int v=tr11111[tmp.id][i].to;
			node tt=node(v,tmp.dis+tr11111[tmp.id][i].w);
			q.push(tt);
		}
	}
	return -1;
}
int main()
{
	int n,m,w,x,y;
	while(~scanf("%d%d",&n,&m))
	{
		for(int i=1;i<=n;i++)
			tr11111[i].clear(),tr2[i].clear();//用tr1一直CE,醉了
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d%d",&x,&y,&w);
			tr11111[x].push_back(Edge(y,w));//正向边
			tr2[y].push_back(Edge(x,w));//反向边
		}
		scanf("%d%d%d",&s,&t,&k);
		if(s==t)//最短路径是0,这条路是不算的 
			k++;
		spfa(t);
		printf("%d\n",astar());
	}
}

A*搜索

HDU2234无题Ⅰ

A*搜索根本就是个刷题看天赋的东西,如果做得太少估值函数就会毫无思路,比如说我。

这题估值函数是使每行或每列变相同的最理想情况的扭转次数。

主要都在注释里面

//这个搜索并不是一直向下搜索,完成或超过5次停止;而是先给一次机会,一次能不能完成;不能再给一次 ,看两次能不能 IDA*搜索
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int m[4][4],deep;
bool check1()//行判断 
{
	for(int i=0;i<4;i++)
	{
		int x=m[i][0];
		for(int j=1;j<4;j++) 
			if(m[i][j]!=x)//不等于该行的第一个 
				return 0;
	}
	return 1;
}
bool check2()//列判断 
{
	for(int i=0;i<4;i++)
	{
		int x=m[0][i];
		for(int j=1;j<4;j++)//不等于该列的第一个 
			if(m[j][i]!=x)
				return 0;
	}
	return 1;
} 
int solve1(int x)//x行出现最多的数 
{
	int num[5]={0,0,0,0,0};
	for(int i=0;i<4;i++)
		num[m[x][i]]++;
	return max(num[4],max(num[3],max(num[2],num[1])));
}
int solve2(int x)//x列出现最多的数 
{
	int num[5]={0,0,0,0,0};
	for(int i=0;i<4;i++)
		num[m[i][x]]++;
	return max(num[4],max(num[3],max(num[2],num[1])));
}
int h()//算出是最理想的状态 
{
	int ans1=0,ans2=0;
	for(int i=0;i<4;i++)
		ans1+=4-solve1(i);//行全相同需要改变的次数 
	for(int i=0;i<4;i++)
		ans2+=4-solve2(i);//列全相同需要改变的次数 
	return (min(ans1,ans2)+3)/4;//扭转一次最多可以改变4个,1,2,3,4扭转1次,5,6,7,8扭转两次,所以要+3之后再除以4 
}
bool dfs(int dep,int pre)
{
	if(dep+h()>deep)//之后最理想需要扭得次数+当前次数>目前给予的deep次 ,直接返回不行 
		return false;
	if(check1()||check2())//符合条件了 
		return 1;
	int tmp[4][4];
	for(int i=0;i<4;i++)//备份,DFS回溯还原 
		for(int j=0;j<4;j++)
			tmp[i][j]=m[i][j];
	for(int i=0;i<16;i++)
	{ 
		if(i+pre==15)//父节点剪枝,上一步向左和下一步向右是重复的,注意向右和向下的i专门去凑15 
			continue;
		if(i<4)//i列向上 
		{	
			int k=m[0][i];
			for(int j=0;j<3;j++)
				m[j][i]=m[j+1][i];
			m[3][i]=k;
		}
		else if(i<8)//i-4行向左 
		{
			int k=m[i-4][0];
			for(int j=0;j<3;j++)
				m[i-4][j]=m[i-4][j+1];
			m[i-4][3]=k;
		}
		else if(i<12)//11-i行向右,不用i-8是为了凑15 
		{
			int k=m[11-i][3];
			for(int j=3;j>0;j--)
				m[11-i][j]=m[11-i][j-1];
			m[11-i][0]=k;
		}
		else//15-i列向下 
		{
			int k=m[3][15-i];
			for(int j=3;j>0;j--)
				m[j][15-i]=m[j-1][15-i];
			m[0][15-i]=k;
		}
		if(dfs(dep+1,i))
			return true;
		else
		{
			for(int i=0;i<4;i++)
				for(int j=0;j<4;j++)
					m[i][j]=tmp[i][j];
		} 
	}
	return false;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		for(int i=0;i<4;i++)
			for(int j=0;j<4;j++)
				scanf("%d",&m[i][j]);
		deep=1;
		while(1)//只扭deep次能否达到目的 
		{
			if(dfs(0,-1)) //达到了就跳出循环 
				break;
			deep++; //当前deep次不能,那再加一次 
			if(deep>5)//超过 5次跳出循环 
			{
				deep=-1;
				break;
			}
		}	
		printf("%d\n",deep);
	}	
}

 

引入

1.康托展开。转自https://blog.youkuaiyun.com/qq_38701476/article/details/81003290

康托展开是一个全排列到一个自然数的双射(可逆),常用于构建哈希表时的空间压缩。(存一个9的全排列,需要开9^9的数组,而康拓可以转换成9!大小) 

康托展开的实质是计算当前排列在所有由小到大全排列中的名次,因此是可逆的。

计算公式   X = A[0] * (n-1)! + A[1] * (n-2)! + … + A[n-1] * 0! 

A[i] 指的是位于位置i后面的数小于A[i]值的个数(逆序对),后面乘的就是后面还有多少个数的阶乘

说明 :这个算出来的数康拖展开值,是在所有排列次序 - 1的值,因此X+1即为在全排列中的次序(增序1234567这样计算出是0,习惯上称为第一个,所以所有都要+1)
 

例:
在(1,2,3,4,5)5个数的排列组合中,计算 34152的康托展开值。
带入上面的公式

X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0!
=>X = 61

 

逆康拖展开
前面已经说到康拖展开是从序列到自然数的映射且是可逆的,那么逆康拖展开便是从自然数到序列的映射
列 :
在(1,2,3,4,5) 给出61可以算出起排列组合为34152
具体过程如下:
用 61 / 4! = 2余13,说明 ,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明 ,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明 ,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明 ,说明在第四位之后小于第四位的数有1个,所以第四位为5。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值