[题解] P3581 [POI2015] CZA

· · 题解

Change log

\color{blueviolet}\text{POI 套题集合(点我)}

\color{red}\text{博客内食用效果更佳(点我)}

时间复杂度:O(n)

完整思路

观察到 p 的范围很小,我们尝试进行分类讨论。

当 p=0:
显著地,当且仅当 n=1 时答案为 1,剩余情况为 0。

当 p=1:
同样显著地,此时当且仅当 n=1\lor(n=2\land k=0) 时答案为 1,剩余情况为 0。

当 p=2:
考虑先放置 n,它两侧只有可能是 n-1,n-2,n-1 旁边只有可能放 n-3,n-2 旁边只有可能放 n-3,以此类推,这种情况下只有顺时针逆时针两种情况,构造并判断即可。

当 p=3:
我们尝试从 n 至 1 依次放进环内,考虑当前放置到编号为 i 的,显然它只能和 i+1,i+2,i+3,i-1,i-2,i-3 相邻,我们只需要考虑 i+1,i+2,i+3,它们都在环内,而 i-1,i-2,i-3 的情况会在放入它们的时候考虑。

我们现在考虑两个问题:

现在给出以下约定方便表述:

对于状态 f_{i+1,j,st} 我们尝试向下转移。

若位置 1 可放置(以下讨论均基于顺时针,逆时针只需要将所有状态置反即可):

$$\left(2\notin st\lor\mathrm{sit}(i+2,i+3)\right)\land\left(3\notin st\lor\mathrm{sit}(i+3,i+1)\right)$$ 放置到位置 $1$ 后我们的状态变为 $f_{i,j\oplus1,5}$(首先这种放置显著会改变顺逆状态,其次这种放置状态下使得 $i,i+1$ 以及 $i,i+2$ 之间的空位可用。),可以得到以下转移。 $$f_{i,j\oplus1,\{1,3\}}=f_{i,j\oplus1,\{1,3\}}+f_{i+1,j,st}$$ **若位置 $2$ 可放置**: 得到以下约束以及转移。 $$\mathrm{sit}(i,i+3)\land\left(2\notin st\lor\mathrm{sit}(i+2,i+3)\right)$$ 设 $st'$ 表示改变后的状态,若 $1\in st$,则 $st'=\{2,3\}$,否则 $st'=\{3\}$。 $$f_{i,j,st'}=f_{i,j,st'}+f_{i+1,j,st}$$ **若位置 $3$ 可放置**: 得到以下约束以及转移。 $$\mathrm{sit}(i+3,i)\land\left(3\notin st\lor\mathrm{sit}(i+3,i+1)\right)$$ 设 $st'$ 表示改变后的状态,若 $1\in st$,则 $st'=\{1,2\}$,否则 $st'=\{1\}$。 $$f_{i,j,st'}=f_{i,j,st'}+f_{i+1,j,st}$$ --- 大部分转移已经讨论完毕,最后一层还需约束放置完的左右关系,与上述约束、转移类似,就不进行冗杂的讨论了。 ### 代码实现需要注意的地方: - 别忘记取模。 - 对于初值有 $f_{n-2,0,7}=1,f_{n-2,1,7}=1$。 ### 参考代码: ```cpp #include<bits/stdc++.h> #define LL long long #define UN unsigned using namespace std; //--------------------// const int N=1e6+5,Mod=1e9+7; int n,k,p,f[N][2][8]; bool ht[N][10]; void add(int &x,int y){x+=y,x-=((x>=Mod)?Mod:0);}//优化取模 bool sit(int x,int y){return abs(x-y)<=p&&!ht[y][y-x+3];}//判断是否能坐在右面 bool ck1(int i,int j,int st,int pos)//判断是否合法 { if(j) { if(pos==1) return ((!(st&4)||sit(i+3,i+1))&&(!(st&2)||sit(i+2,i+3))); if(pos==2) return (sit(i,i+3)&&(!(st&4)||sit(i+3,i+1))); return (sit(i+3,i)&&(!(st&2)||sit(i+2,i+3))); } if(pos==1) return (!(st&4)||sit(i+1,i+3))&&(!(st&2)||sit(i+3,i+2)); if(pos==2) return (sit(i+3,i)&&(!(st&4)||sit(i+1,i+3))); return (sit(i,i+3)&&(!(st&2)||sit(i+3,i+2))); } bool ck2(int i,int j,int st,int pos)//判断 i=1 时是否合法 { if(i>1) return true; if(j) { if(pos==1) return (sit(1,3)&&sit(2,1)); if(pos==2) return (sit(3,1)&&(!(st&1)||sit(2,3))); return (sit(1,2)&&(!(st&1)||sit(2,3))); } if(pos==1) return (sit(3,1)&&sit(1,2)); if(pos==2) return (sit(1,3)&&(!(st&1)||sit(3,2))); return (sit(2,1)&&(!(st&1)||sit(3,2))); } //--------------------// int main() { scanf("%d%d%d",&n,&k,&p); for(int x,y,i=1;i<=k;i++) { scanf("%d%d",&x,&y); if(abs(x-y)<=p) ht[x][x-y+3]=true; } if(p==0)//分讨 { if(n==1) printf("1"); else printf("0"); return 0; } if(p==1) { if(n==1||(n==2&&k==0)) printf("1"); else printf("0"); return 0; } if(n==1) printf("1"); if(n==2) printf("%d",k?0:1); if(n<3) return 0; if(p==2) { int ans=0; //顺时针 bool flag=((!sit(n-1,n))|((n&1)&&!sit(1,2))|((!(n&1))&&!sit(2,1))); for(int i=n-1;i>2;i-=2) flag|=!sit(i-2,i); for(int i=n-2;i>2;i-=2) flag|=!sit(i,i-2); //逆时针 ans+=!flag,flag=((!sit(n,n-1))|((n&1)&&!sit(2,1))|((!(n&1))&&!sit(1,2))); for(int i=n-1;i>2;i-=2) flag|=!sit(i,i-2); for(int i=n-2;i>2;i-=2) flag|=!sit(i-2,i); ans+=!flag; printf("%d",ans); return 0; } f[n-2][0][7]=f[n-2][1][7]=1; for(int i=n-2;i>=2;i--)//倒序转移 { for(int j=0;j<=1;j++) { for(int st=0;st<=7;st++) { if(!f[i][j][st]) continue; if((st&1)&&ck1(i-1,j,st,1)&&ck2(i-1,j,st,1)) add(f[i-1][j^1][5],f[i][j][st]); if((st&2)&&ck1(i-1,j,st,2)&&ck2(i-1,j,st,2)) add(f[i-1][j][4|((st&1)<<1)],f[i][j][st]); if((st&4)&&ck1(i-1,j,st,3)&&ck2(i-1,j,st,3)) add(f[i-1][j][1|((st&1)<<1)],f[i][j][st]); } } } int ans=0; for(int i=0;i<=7;i++)//统计答案 add(ans,f[1][0][i]),add(ans,f[1][1][i]); printf("%d",ans); return 0; } ```