manacher模板
就模板
#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*搜索
求第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*搜索
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。