插头dp的几个模板

/*
ural1519
求经过所有可行点的哈密顿回路的个数
括号匹配法,转移有点复杂,但是时间空间比较小
*/
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<map>
#include<queue>
#define LL long long
using namespace std;
const int maxn=30001;
int n,m,now,pre;
int mov[13]={0,2,4,6,8,10,12,14,16,18,20,22,24};//根据进制选择移位距离
char gp[20][20],fx,fy;//存图,最后一个可行点的坐标
inline int getbit(LL st,int k){ return (st>>mov[k])&3;}//获得第k位的状态
inline int pybit(LL st,int k){ return st<<mov[k];}     //平移k位
inline LL clrbit(LL st,int i,int j){ return st&(~(3<<mov[i]))&(~(3<<mov[j]));}//清空第i位和第j位
struct node//状态离散hash
{
	int head[maxn],next[maxn],size;
	LL sum[maxn],sta[maxn];//保存所求和及状态
	void clear()
	{
		memset(head,-1,sizeof(head));
		memset(sum,0,sizeof(sum));
		size=0;
	}
	void push(LL st,const LL v)
	{
		LL hash=st%maxn;
		for(int i=head[hash];i>=0;i=next[i])
		{
			if(sta[i]==st)
			{
				sum[i]+=v;
				return;
			}
		}
		sta[size]=st,sum[size]=v;
		next[size]=head[hash],head[hash]=size++;
	}
}dp[2];
inline int fl(LL st,int pos)//从左往右找到和当前pos位置匹配的右括号
{
	int cnt=1;
	for(int i=pos+1;i<=m;i++)
	{
		int k=getbit(st,i);
		if(k==1) cnt++;
		else if(k==2) cnt--;
		if(!cnt) return i;
	}
}
inline int fr(LL st,int pos)//从右往左找到和当前pos位置匹配的左括号
{
	int cnt=1;
	for(int i=pos-1;i>=0;i--)
	{
		int k=getbit(st,i);
		if(k==2) cnt++;
		else if(k==1) cnt--;
		if(!cnt) return i;
	}
}
void DP(int x,int y,int k)//每种状态的转移,根据需要修改
{	
	int l=getbit(dp[pre].sta[k],y-1);
	int up=getbit(dp[pre].sta[k],y);
	LL st=clrbit(dp[pre].sta[k],y-1,y);
	LL v=dp[pre].sum[k];
	if(!l&&!up)	
	{	
		if(gp[x][y]=='*')
		{
			dp[now].push(st,v);
			return;
		}
		if(x<n&&y<m&&gp[x+1][y]=='.'&&gp[x][y+1]=='.')
			dp[now].push(st|pybit(1,y-1)|pybit(2,y),v);
	}
	else if(!l||!up)
	{
		int e=l+up;
		if(x<n&&gp[x+1][y]=='.')
			dp[now].push(st|pybit(e,y-1),v);
		if(y<m&&gp[x][y+1]=='.')
			dp[now].push(st|pybit(e,y),v);
	}
	else if(l==1&&up==1)
		dp[now].push(st^pybit(3,fl(st,y)),v);
	else if(l==2&&up==2)
		dp[now].push(st^pybit(3,fr(st,y-1)),v);
	else if(l==2&&up==1)
		dp[now].push(st,v);
	else if(x==fx&&y==fy)
		dp[now].push(st,v);
}
LL solve()
{
	dp[0].clear();//初状态
	dp[0].push(0,1);
	now=0,pre=1;
	for(int i=1;i<=n;i++)//逐格逐状态枚举
	{
		pre=now,now^=1,dp[now].clear();
		for(int k=0;k<dp[pre].size;k++)//轮廓线下移对齐
			dp[now].push(pybit(dp[pre].sta[k],1),dp[pre].sum[k]);
		for(int j=1;j<=m;j++)
		{
			pre=now,now^=1,dp[now].clear();
			for(int k=0;k<dp[pre].size;k++)
			{
				DP(i,j,k);
			}
		}
	}
	for(int i=0;i<dp[now].size;i&#
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
插头DP是一种常见的动态规划算法,主要用于求解一些有依赖关系的问题,如图论中的最小路径覆盖、字符串匹配中的最长公共子序列等等。 插头DP的主要思想是将原问题转化为一个有向无环图(DAG)上的最长路径问题,其中每个节点表示原问题中的一个状态,每个边表示从一个状态转移到另一个状态的操作。插头DP的核心是“插头”,即在DAG中插入一些边来保证每个状态只被计算一次。 下面给出插头DP模板代码,并详细解释每一部分的含义: ```cpp const int N = 100010; int n, m, idx; // idx表示DAG中节点的数量 int h[N], e[N], ne[N], idx; // 邻接表存储DAG int f[N]; // f[i]表示以i为终点的最长路径 int g[N]; // g[i]表示以i为起点的最长路径 int q[N], d[N]; // q存储拓扑序列,d[i]表示i的入度 bool st[N]; // st[i]表示i是否在拓扑序列中 void add(int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } void topsort() { int hh = 0, tt = -1; // 将所有入度为0的点加入队列 for (int i = 1; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; // 拓扑排序 while (hh <= tt) { int t = q[hh ++ ]; st[t] = true; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (-- d[j] == 0) q[ ++ tt] = j; } } } int main() { memset(h, -1, sizeof h); // 读入图 scanf("%d%d", &n, &m); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(a, b); d[b] ++ ; } // 求出DAG中的拓扑序列 topsort(); // 计算每个节点的g数组 for (int i = 0; i < idx; i ++ ) { int j = e[i]; if (st[j]) g[j] = max(g[j], f[e[i ^ 1]] + 1); } // 计算每个节点的f数组 for (int i = n; i; i -- ) { int j = q[i]; for (int k = h[j]; ~k; k = ne[k]) f[j] = max(f[j], g[e[k]] + 1); } // 求最长路径 int res = 0; for (int i = 1; i <= n; i ++ ) res = max(res, f[i]); printf("%d\n", res); return 0; } ``` 1. 声明变量 ```cpp const int N = 100010; int n, m, idx; // idx表示DAG中节点的数量 int h[N], e[N], ne[N], idx; // 邻接表存储DAG int f[N]; // f[i]表示以i为终点的最长路径 int g[N]; // g[i]表示以i为起点的最长路径 int q[N], d[N]; // q存储拓扑序列,d[i]表示i的入度 bool st[N]; // st[i]表示i是否在拓扑序列中 ``` 2. 存储图并求拓扑序列 ```cpp void add(int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } void topsort() { int hh = 0, tt = -1; // 将所有入度为0的点加入队列 for (int i = 1; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; // 拓扑排序 while (hh <= tt) { int t = q[hh ++ ]; st[t] = true; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (-- d[j] == 0) q[ ++ tt] = j; } } } int main() { memset(h, -1, sizeof h); // 读入图 scanf("%d%d", &n, &m); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(a, b); d[b] ++ ; } // 求出DAG中的拓扑序列 topsort(); } ``` 首先定义一个add函数,用于存储原图的边,同时记录每个节点的入度。然后定义一个topsort函数,用于求出DAG的拓扑序列。具体实现是通过队列来实现的,首先将所有入度为0的点加入队列,然后依次取出队首节点,并将与之相连的节点的入度减1,若入度减为0,则将该节点加入队列。最终得到的队列即为DAG的拓扑序列。 3. 计算每个节点的g数组 ```cpp for (int i = 0; i < idx; i ++ ) { int j = e[i]; if (st[j]) g[j] = max(g[j], f[e[i ^ 1]] + 1); } ``` 对于每个节点j,遍历与之相连的所有入边,对应的起点为e[i ^ 1](由于存储原图和存储DAG的边是交替存储的,所以需要异或1),若起点在拓扑序列中,则更新g[j]的值。 4. 计算每个节点的f数组 ```cpp for (int i = n; i; i -- ) { int j = q[i]; for (int k = h[j]; ~k; k = ne[k]) f[j] = max(f[j], g[e[k]] + 1); } ``` 对于每个节点j,遍历与之相连的所有出边,对应的终点为e[k],更新f[j]的值。 5. 求最长路径 ```cpp int res = 0; for (int i = 1; i <= n; i ++ ) res = max(res, f[i]); printf("%d\n", res); ``` 遍历所有节点,找到以每个节点为终点的最长路径,取最大值即为DAG的最长路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值