[JOISC2016]サンドイッチ

题目大意:
  一个$n\times m(n,m\leq400)$的网格图中,每个格子上放了两个三明治,摆放的方式分为'N'和'Z'两种。一个三明治可以被拿走当且仅当与该三明治的两条直角边相邻的三明治均被拿走或与该三明治斜边相邻的三明治被拿走。问对于每个格子,想要拿走这个格子中的所有三明治至少需要先拿走多少三明治。

思路:
  对于同一个格子,不难发现这一个格子中两个三明治接连被拿走一定是最优的。
  于是这题就和每个单独的三明治取走顺序没什么关系了,只和每个方格取走顺序及三明治的摆放方式有关。
  $O(n^2)$枚举每个格子$(x,y)$,假设它是因为$(x-1,y)$和$(x,y-1)$被取走后才被取走,我们可以$O(n^2)$DFS出最优情况下,取走每个格子之前一定要取走哪些格子。时间复杂度$O(n^4)$,bitset优化为$O(\frac{n^4}{\omega})$。
  不难发现,若$(x,y)$是因为$(x-1,y)$被取走才被取走的,$(x-1,y)$不可能因为$(x,y)$被取走才被取走。因此对于同一行的格子,我们可以让后面的DFS重复利用前面DFS出的信息。DFS是$O(n^2)$的,每一行要重新DFS,时间复杂度是$O(n^3)$。
  具体实现上,可以用$0\sim3$来表示不同的方向。若摆放方式为'N'的格子,一个直角边的方向为$d$,则另一个直角边的方向为$d\oplus3$;若摆放方式为'Z'的格子,一个直角边的方向为$d$,则另一个直角边的方向为$d\oplus1$。搜索时的一系列分类讨论可以通过简单的位运算实现。

 1 #include<cstdio>
 2 #include<cctype>
 3 #include<algorithm>
 4 inline int getint() {
 5     register char ch;
 6     while(!isdigit(ch=getchar()));
 7     register int x=ch^'0';
 8     while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
 9     return x;
10 }
11 inline bool getblock() {
12     register char ch;
13     while(!isalpha(ch=getchar()));
14     return ch=='N';
15 }
16 const int N=400,inf=0x7fffffff;
17 const int dx[]={0,-1,0,1},dy[]={-1,0,1,0};
18 bool a[N][N];
19 int n,m,f[N][N],ans[N][N],tmp,flag;
20 inline bool check(const int &x,const int &y) {
21     return x>=0&&x<n&&y>=0&&y<m;
22 }
23 void dfs(const int &x,const int &y,const int &d) {
24     if(!~f[x][y]) {
25         flag=true;
26         return;
27     }
28     if(f[x][y]) return;
29     tmp+=2;
30     f[x][y]=-1;
31     const int p=a[x][y]?3:1;
32     if(check(x-dx[d],y-dy[d])) dfs(x-dx[d],y-dy[d],d);
33     if(check(x-dx[d^p],y-dy[d^p])) dfs(x-dx[d^p],y-dy[d^p],d^p);
34     f[x][y]=1;
35 }
36 int main() {
37     for(register int T=getint();T;T--) {
38         n=getint(),m=getint();
39         for(register int i=0;i<n;i++) {
40             for(register int j=0;j<m;j++) {
41                 a[i][j]=getblock();
42             }
43         }
44         for(register int i=0;i<n;i++) {
45             flag=tmp=0;
46             for(register int i=0;i<n;i++) {
47                 for(register int j=0;j<m;j++) {
48                     f[i][j]=0;
49                 }
50             }
51             for(register int j=0;j<m;j++) {
52                 if(!flag) dfs(i,j,2);
53                 ans[i][j]=flag?inf:tmp;
54             }
55             flag=tmp=0;
56             for(register int i=0;i<n;i++) {
57                 for(register int j=0;j<m;j++) {
58                     f[i][j]=0;
59                 }
60             }
61             for(register int j=m-1;~j;j--) {
62                 if(!flag) dfs(i,j,0);
63                 ans[i][j]=std::min(ans[i][j],flag?inf:tmp);
64             }
65         }
66         for(register int i=0;i<n;i++) {
67             for(register int j=0;j<m;j++) {
68                 printf("%d%c",ans[i][j]!=inf?ans[i][j]:-1," \n"[j==m-1]);
69             }
70         }
71     }
72     return 0;
73 }

 

转载于:https://www.cnblogs.com/skylee03/p/8413400.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值