1. 程式人生 > 其它 >2021長安部分題解

2021長安部分題解

pd頭插

用途

插頭dp主要是用來解決基於連通性狀態壓縮的動態規劃問題,一般來說,就是解決一個網格圖中的迴路方案數的問題,並且資料範圍較小,比如這道模板

方法

定義插頭:路徑是否經過格點的邊,如圖就是一個左插頭

不難發現,對於一個迴路上的所有點,都有且只有兩個插頭,如圖

於是我會爆搜\(\Theta(6^{nm})\)

這顯然不現實,考慮dp轉移

轉移的物件

考慮轉移輪廓線,也就是說一條將我們已經考慮的半圖和未考慮的半圖分開的線,如圖中綠線,藍線表示已經考慮

通常來說,我們採取逐格遞推(少數情況下,我們會考慮逐行遞推)

狀態的表示

最小表示法

障礙為0,第一個連通塊內編號為1,第二個連通塊內編號為2……

(還有一種是將每個連通塊標記成最左邊的列編號)

括號表示法

輪廓線上從左到右4個插頭 a, b, c, d,如果 a, c連通,並且與 b 不連通,那麼 b, d 一定不連通。(對所有棋盤問題都適用,詳見CDQ論文)

那麼不難聯想到括號匹配,用0表示無括號,1,表示左括號,2表示右括號(實現中一般採用4進位制,位運算真香)

一些trival的細節

表示編碼一般考慮用map或hash滾動dp,要不然記憶體會爆

大致方式

就是分類瘋累討論,一個格子只有上下,左右,上左,上右,下左,下右6種情況,於是討論就完了,貼上模板的程式碼

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

namespace DEBUG {
	void debug_out() { cerr << '\n'; }
	template <typename Head, typename... Tail>
	void debug_out(Head H, Tail... T) { cerr << ' ' << H, debug_out(T...); }
#define debug(...) cerr << '[' << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
} using namespace DEBUG;
typedef long long ll;
typedef unsigned long long ull;
//#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++)
namespace get_out
{
	char B[1<<15],*S=B,*T=B;
	char op;
	inline void read_(int &x)
	{
		x=0;
		int fi(1);
		op=getchar();
		while((op<'0'||op>'9')&&op!='-') op=getchar();
		if(op=='-') op=getchar(),fi=-1;
		while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar();
		x*=fi;
		return;
	}
	inline void read_(long long &x)
	{
		x=0;
		int fi(1);
		op=getchar();
		while((op<'0'||op>'9')&&op!='-') op=getchar();
		if(op=='-') op=getchar(),fi=-1;
		while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar();
		x*=fi;
		return;
	}
	inline void read_(double &x)
	{
		x=0.0;
		float fi(1.0),sm(0.0);
		op=getchar();
		while((op<'0'||op>'9')&&op!='-') op=getchar();
		if(op=='-') op=getchar(),fi=-1.0;
		while(op>='0'&&op<='9') x=(x*10.0)+(op^48),op=getchar();
		if(op=='.') op=getchar();
		int rtim=0;
		while(op>='0'&&op<='9') sm=(sm*10.0)+(op^48),++rtim,op=getchar();
		while(rtim--) sm/=10.0;
		x+=sm,x*=fi;
		return;
	}
	inline void postive_write(int x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void postive_write(long long x)
	{
		if(x>9) postive_write(x/10);
		putchar(x%10|'0');
	}
	inline void write_(int x)
	{
		if(x<0) x=-x,putchar('-');
		postive_write(x);
	}
	inline void write_(int x,char ch)
	{
		write_(x),putchar(ch);
	}
	inline void write_(long long x)
	{
		if(x<0) x=-x,putchar('-');
		postive_write(x);
	}
	inline void write_(long long x,char ch)
	{
		write_(x),putchar(ch);
	}
}
using get_out::read_;
using get_out::write_;
#define maxn 15
#define inf 0x7f7f7f7f
#define mod 299993
#define hashMaxNum 300000

int n,m,head[hashMaxNum];
int cnt[2],mp[maxn][maxn],edi,edj,now,la;
ll ans=0;

class hahaha
{
	public:
		int s[20];
		hahaha()=default;
		hahaha(int state)
		{
			s[0]=state&3;
			for(int i=1;i<=m;++i) s[i]=(state>>(i<<1))&3;
		}
		inline int rar()
		{
			int state=0;
			for(int i=1;i<=m;++i) state|=s[i]<<(i<<1);
			return (state|s[0]);
		}
};

inline hahaha unpack(int state) {return hahaha(state);}
inline int rar(hahaha &H){return H.rar();}

class hash_T
{
	public:
		int state[2],nex;
		ll num[2];
		hash_T()=default;
}s[hashMaxNum];

inline void insert(int state,ll num)
{
	int tmp=state%mod;
	for(int i=head[tmp];i;i=s[i].nex)
		if(state==s[i].state[now])
		{
			s[i].num[now]+=num;
			return;
		}
	s[++cnt[now]].state[now]=state,
	s[cnt[now]].nex=head[tmp],
	s[head[tmp]=cnt[now]].num[now]=num;
}
hahaha now_state,_k;
ll lnum;

inline void zip_insert()
{
	insert(rar(_k),lnum),_k=now_state;
}

inline void solve()
{
	register int i,j,k;
	int west,north;
	insert(0,1);
	for(i=1;i<=n;++i) for(j=1;j<=m;++j)
	{
		la=now,now^=1;//renew state
		cnt[now]=0,memset(head,0,sizeof(head));//init
		for(k=1;k<=cnt[la];++k)
		{
			now_state=unpack(s[k].state[la]),_k=now_state;
			lnum=s[k].num[la],west=now_state.s[0],north=now_state.s[j];
			if(!mp[i][j]) {if(!west&&!north) insert(rar(_k),lnum);continue;}
			if(!west&&!north)
			{
				if(mp[i][j+1]&&mp[i+1][j])
					_k.s[0]=2,
					_k.s[j]=1,//掛過 
					zip_insert();
				continue;
			}
			if(!west&&north)
			{
				if(mp[i+1][j])
					insert(rar(_k),lnum);
				if(mp[i][j+1])
					_k.s[0]=north,
					_k.s[j]=0,
					zip_insert();
				continue;
			}
			if(west&&!north)
			{
				if(mp[i][j+1])
					insert(rar(_k),lnum);
				if(mp[i+1][j])
					_k.s[0]=0,
					_k.s[j]=west,
					zip_insert();
				continue;
			}
			if(west==1&&north==2)
			{
				_k.s[0]=_k.s[j]=0;
				bool flag=0;
				for(int pos=0;pos<=m;++pos)
					if(_k.s[pos])
					{
						flag=1;
						break;
					}
				if(!flag&&i==edi&&j==edj) ans+=lnum;
				continue;
			}
			if(west==2&&north==1)
			{
				_k.s[0]=_k.s[j]=0;
				zip_insert();
				continue;
			}
			if(west==1&&north==1)
			{
				int nm=1,pos=j+1;
				for(;pos<=m;++pos)
				{
					nm+=_k.s[pos]==1?1:(_k.s[pos]==2?-1:0);
					if(!nm) break;
				}
				_k.s[pos]=1;
				_k.s[0]=_k.s[j]=0;
				zip_insert();
				continue;
			}
			if(west==2&&north==2)
			{
				int nm=-1,pos=j-1;
				for(;pos;--pos)
				{
					nm+=_k.s[pos]==1?1:(_k.s[pos]==2?-1:0);
					if(!nm) break;
				}
				_k.s[pos]=2;
				_k.s[0]=_k.s[j]=0;
				zip_insert();
				continue;
			}
			
		}
	}
}

signed main()
{
	register char ch[20];
	read_(n),read_(m);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",ch+1);
		for(int j=1;j<=m;++j)
			((ch[j]=='.'&&(mp[i][j]=1,edi=i,edj=j))||(mp[i][j]=0));
	}
	solve();
	cout<<ans<<'\n';
	
	return 0;
}