HDU 4012 Paint on a Wall

举报
Linux猿 发表于 2021/08/06 00:41:41 2021/08/06
【摘要】 题目链接~~> 做题感悟:这题只能说很“ 经典 ” 。 解题思路:题意就是给你一个二行N例的一面墙,在墙上涂颜色,问你最少粉刷多少次,能达到给你的目标墙的状态,(每次粉刷只能粉刷出一个矩形,矩形可以是一行N例,也可以是二行N例); 思想:广度优先搜索(因为找最短路) 状态表示: 状态用二制位表示。例如当N=4时,就用8位二进制数表示...

题目链接~~>

做题感悟:这题只能说很“ 经典 ” 。

解题思路:题意就是给你一个二行N例的一面墙,在墙上涂颜色,问你最少粉刷多少次,能达到给你的目标墙的状态,(每次粉刷只能粉刷出一个矩形,矩形可以是一行N例,也可以是二行N例);

思想:广度优先搜索(因为找最短路)
状态表示:
状态用二制位表示。例如当N=4时,就用8位二进制数表示状态如:
1100
1010
‘‘0’’表示与目标状态颜色不一致,‘1’表示一致。
初始状态当然为:
0000
0000
因为和目标状态没有相同颜色。
目标状态自然为:
1111
1111
状态转移:
状态转移就是粉刷墙的过程,按传统套路很容易把粉刷的颜色也考虑进去。这样就走弯路了。
仔细想想会发现 每次粉刷,都会使一状态中的一些0变成1。从而越来越接近目标状态。
问题来了,每次粉刷多大?粉刷的大小受什么影响?
先分两种情况:
第一种是刷一行,在图上选一个点的颜色粉刷,分别向该点的左右粉刷,如果遇到和选取点颜色相同,则将当前状态该位的0变成1,表示与目标状态相同
第二种是刷两行,刷两行只考虑一行就可以,在其中一行寻解时,同时考虑同列的另一行,也就是在列相同的情况下判断两行的颜色,以及粉刷。
这样就可以从一个状态转移到另一个状态。
注:上述方法可以得到一个极大粉刷区域, 每次状态转移全是转移最大粉刷区域,显然是不够准确的,算出最大粉刷区域后应该也将该区域的子集加到扩展队列中。求一个状态的子集的方法:整数temp的位数为当前状态 ,那么for(int j=temp;j;j=temp&(j-1));j就为子状态。可以自己模拟体会一下。
判重:
根据状态抽象,最多有2<16种状态,可以开一个bool型数组flag[1<<16];如flag[i]=1,则表示i二进制位表示的这个状态以经出现过。
代码:

    
  1. #include<stdio.h>
  2. #include<iostream>
  3. #include<map>
  4. #include<stack>
  5. #include<string>
  6. #include<string.h>
  7. #include<stdlib.h>
  8. #include<math.h>
  9. #include<vector>
  10. #include<queue>
  11. #include<algorithm>
  12. using namespace std ;
  13. #define LEN sizeof(struct node)
  14. #define pret(a,b) memset(a,b,sizeof(a))
  15. #define lld __int64
  16. const double PI = 3.1415926 ;
  17. const double INF = 999999999 ;
  18. const double esp = 1e-4 ;
  19. const lld md= 2810778 ;
  20. const int MX = 20 ;
  21. int n ;
  22. char s[MX] ;
  23. bool vis[1<<16] ;
  24. struct node
  25. {
  26. int key,step ; // key 表状态 step 记录步数
  27. } ;
  28. int bfs()
  29. {
  30. queue<node>q ;
  31. node curt,next ;
  32. while(!q.empty()) q.pop() ;
  33. memset(vis,false,sizeof(vis)) ;
  34. curt.key=curt.step=0 ;
  35. vis[0]=true ;
  36. q.push(curt) ;
  37. while(!q.empty())
  38. {
  39. curt=q.front() ;
  40. q.pop() ;
  41. for(int i=0 ;i<(n<<1) ;i++) // 遍历每一个点
  42. {
  43. int key=curt.key,ky=0 ;
  44. next.step=curt.step+1 ; // 步数加一
  45. if(key&(1<<i)) continue ;
  46. // 先单行左右扩展
  47. for(int j=i ;j<(i/n+1)*n ;j++)// 从当前节点向右扩展
  48. {
  49. if(key&(1<<j)) break ;// 遇到已经染色的就结束
  50. if(s[i]==s[j]) ky=ky|(1<<j) ; // 与起始颜色一样 ~> 染色
  51. }
  52. for(int j=i-1 ;j>=(i/n)*n ;j--) // 从当前节点向左扩展
  53. {
  54. if(key&(1<<j)) break ;
  55. if(s[i]==s[j]) ky=ky|(1<<j) ;
  56. }
  57. if((key|ky)==(1<<(n*2))-1) return next.step ; // 放在这可以减少时间
  58. for(int j=ky ;j ;j=key&(j-1))
  59. {
  60. if(!vis[key|j])
  61. {
  62. vis[key|j]=true ;
  63. next.key=key|j ;
  64. q.push(next) ;
  65. }
  66. }
  67. // 以上为单行双向扩展
  68. if(i/n) continue ;
  69. key=curt.key ; ky=0 ;
  70. if(key&(1<<(i+n))) continue ;
  71. for(int j=i ;j<n ;j++)
  72. {
  73. if((key&(1<<j))||(key&(1<<(j+n)))) break ;
  74. if(s[i]==s[j]) ky=ky|(1<<j) ;
  75. if(s[i]==s[j+n])ky=ky|(1<<(j+n)) ;
  76. }
  77. for(int j=i-1 ;j>=0 ;j--)
  78. {
  79. if((key&(1<<j))||(key&(1<<(j+n)))) break ;
  80. if(s[i]==s[j]) ky=ky|(1<<j) ;
  81. if(s[i]==s[j+n]) ky=ky|(1<<(j+n)) ;
  82. }
  83. if((key|ky)==(1<<(n*2))-1) return next.step ;
  84. for(int j=ky ;j ;j=ky&(j-1)) // 切记要枚举子集:因为并不一定需要涂最大区域,如果涂了最大区域
  85. { //别的地方就没法连续涂了。
  86. if(!vis[key|j])
  87. {
  88. vis[key|j]=true ;
  89. next.key=key|j ;
  90. q.push(next) ;
  91. }
  92. }
  93. }
  94. }
  95. return 0 ;
  96. }
  97. int main()
  98. {
  99. int Tx,q=1 ;
  100. scanf("%d",&Tx) ;
  101. while(Tx--)
  102. {
  103. scanf("%d",&n) ;
  104. scanf("%s",s) ; // 用一维存储
  105. scanf("%s",s+n) ;
  106. printf("Case #%d: %d\n",q++,bfs()) ;
  107. }
  108. return 0 ;
  109. }

 

文章来源: blog.csdn.net,作者:Linux猿,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/nyist_zxp/article/details/23338569

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。