把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3581】[POI2015] CZA(DP)

点此看题面

  • \(n\)个人围着圆桌坐成一圈,要求满足相邻两人编号差不超过\(p\),且存在\(k\)对矛盾关系形如\(b_i\)不能坐在\(a_i\)右边。
  • 假设第\(n\)个人位置固定,求排座位的方案数。
  • \(n\le10^6,k\le10^5,0\le p\le 3\)

\(n\le2\)\(p\le2\)的特判

显然\(n=1\)时答案为\(1\)\(n=2\)时若\(p\ge1\)\(k=0\)则答案为\(1\),否则答案为\(0\)

否则,当\(n\ge 3\)时,若\(p\le1\)显然无解。

\(p=2\)时实际上只有两种顺序相反的放法:\(n\)的一侧放\(n-2,n-4,...\),另一侧放\(n-1,n-3,...\)。直接分别检验两种情况是否可行即可。

于是接下来就只需要考虑\(n\ge 3\)\(p=3\)的情况了。

插入式\(DP\)

一种比较经典的\(DP\)模型。

本题有一个性质,在一个合法方案中,最小数两侧的数一定相差不超过\(p\),即一个合法局面在删去最小数之后仍是合法局面

又由于最小数\(i\)只有可能插在\(i+1,i+2,i+3\)三个数之间,方案数相对较少,因此我们不妨从大到小一个一个插入数进行\(DP\)

即,设\(f_{i,0/1/2,0/1/2,0/1/2}\)表示最小值为\(i\)\(i\)\(i+1\)不相邻/\(i\)\(i+1\)右/\(i\)\(i+1\)左,\(i+1\)\(i+2\)不相邻/\(i+1\)\(i+2\)右/\(i+1\)\(i+2\)左,\(i+2\)\(i\)不相邻/\(i+2\)\(i\)右/\(i+2\)\(i\)的方案数。

初始假设已经放好了\(n-2,n-1,n\)

转移时首先要判断\(i+1,i+2\)\(i+3\)之间是否存在不合法的位置关系,如果有必须断开,且\(i\)不能与\(i+3\)产生新的不合法关系,因为之后的数无法再插在\(i+3\)旁边了。而\(i,i+1,i+2\)三者间暂时有不合法的位置关系是允许的,因为之后可能被断开。

如果没有强制要求,简单讨论一波转移即可。

代码:\(O(n3^p)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 1000000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,k,p,s[N+5],w[N+5][7],f[2][3][3][3];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
}using namespace FastIO;
int main()
{
	RI i,j,x,y,z;for(read(n),read(k),read(p),i=1;i<=k;++i) read(x),read(y),abs(y-x)<=p&&(w[x][y-x+p]=1);
	if(n==1) return puts("1"),0;if(n==2) return puts(p>=1&&!k?"1":"0"),0;if(p<=1) return puts("0"),0;//特判n≤2和p≤1
	if(p==2)//特判n=2,只有两种顺序相反的方法
	{
		for(s[x=1]=n,i=n-1;i>=1;i-=2) s[++x]=i;for(i=n&1?1:2;i<n;i+=2) s[++x]=i;s[n+1]=s[1];
		for(i=1;i<=n;++i) if(w[s[i]][s[i+1]-s[i]+p]) break;RI ans=i>n;reverse(s+2,s+n+1);
		for(i=1;i<=n;++i) if(w[s[i]][s[i+1]-s[i]+p]) break;return printf("%d\n",ans+(i>n)),0;
	}
	for(f[n&1][1][1][1]=f[n&1][2][2][2]=1,i=n-3;i;--i)//DP,初始放好n-2,n-1,n
	{
		for(x=0;x^3;++x) for(y=0;y^3;++y) for(z=0;z^3;++z) f[i&1][x][y][z]=0;//清空
		for(x=0;x^3;++x) for(y=0;y^3;++y) for(z=0;z^3;++z) if(f[i&1^1][x][y][z])
		{
			if((y==1&&w[i+2][4]||y==2&&w[i+3][2])&&(z==1&&w[i+3][1]||z==2&&w[i+1][5])) continue;//如果i+1,i+2都和i+3有关系,无法全部断开
			#define T1 !w[i][6]&&Inc(f[i&1][0][x][1],f[i&1^1][x][y][z])//插在i+2和i+3之间
			#define T2 !w[i+3][0]&&Inc(f[i&1][0][x][2],f[i&1^1][x][y][z])//插在i+3和i+2之间
			#define T3 !w[i+3][0]&&Inc(f[i&1][1][x][0],f[i&1^1][x][y][z])//插在i+3和i+1之间
			#define T4 !w[i][6]&&Inc(f[i&1][2][x][0],f[i&1^1][x][y][z])//插在i+1和i+3之间
			if(y==1&&w[i+2][4]) {T1;continue;}if(y==2&&w[i+3][2]) {T2;continue;}//i+1和i+3存在不合法关系
			if(z==1&&w[i+3][1]) {T3;continue;}if(z==2&&w[i+1][5]) {T4;continue;}//i+2和i+3存在不合法关系
			y==1&&T1,y==2&&T2,z==1&&T3,z==2&&T4;//先前四种转移
			x==1&&Inc(f[i&1][2][0][2],f[i&1^1][x][y][z]),x==2&&Inc(f[i&1][1][0][1],f[i&1^1][x][y][z]);//插在i+1和i+2之间;插在i+2和i+1之间
		}
	}
	RI ans=0;for(x=0;x^3;++x) for(y=0;y^3;++y) for(z=0;z^3;++z)//统计答案
		x==1&&w[1][4]||x==2&&w[2][2]||y==1&&w[2][4]||y==2&&w[3][2]||z==1&&w[3][1]||z==2&&w[1][5]||Inc(ans,f[1][x][y][z]);//最终状态不能存在任何不合法关系
	return printf("%d\n",ans),0;
}
posted @ 2021-07-22 17:23  TheLostWeak  阅读(147)  评论(1编辑  收藏  举报