果然还是先用女神镇宅比较好~
进入正题:
原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=1882
这个题我想起了好小的时候玩的关灯游戏,改变一盏灯的状态(开或者关),相邻的灯的状态随之改变(开变关,关变开)。
题意就是给你一个 r * c 的最开始的状态,X代表黑, . 代表白。
翻转(?)一张卡片(ps:大雾,其实我没读题,只是看了样例,也不管是翻还是关了。英语不好伤不起)会引起相邻卡片状态的变化。
问,最少需要多少步,才能把这个 r * c 牌阵(想不到别的词了T-T)翻转成全部白色,也就是“ . ”。
第一眼看到,每个格子只有两种状态,黑和白,所以想到了状态压缩,于是想到了状压DP。但是苦思冥想很久,没想出状态转移方程。
但是仔细想想,用枚举会更加直观简便。状态压缩,解决内存问题,16*16应该不容易超时。于是决定开始动工。
首先,第一行的策略使用枚举:第一行不翻转,翻转第一张,翻转第一第二张……翻转全部,最坏情况要枚举16*16次。
第一行的状态定下来之后,后面的策略也就唯一了!比如:
我们用1表示黑,0表示白
5 5
11011
10101
01110
10101
11011
枚举第一行的策略。首先,如果第一行一张都不翻。
那么第一行的状态就是:
11011
那么在第二行,就必须要把上一行的黑牌翻转。第三行的策略已经无法影响第一行了。
所以,第二行就要翻转上一行1对应的位置的牌,使得上一行的牌翻转。变成:↓
00000
10101
10101
10101
11011
同理,第三行的策略,也是要把第二行的1翻转为0,的四行也是……直到最后一行,策略依然是把上一行的黑牌翻转。
那么方案可不可行的判断条件就是,当最后一行把上一行的黑牌翻转之后,自身也全变为白色,那么就是可行的方案,否则不可行。之后取最小值方可。
然而当我理清思路,很欢快地码完了代码,通过样例一气呵成,提交上去的时候,TLE了……
整个人都不好了……(最近一段时间总是TLE,心塞塞的)
状态压缩一般不会MLE,那么时间问题上,我们可以作两个优化,以保证不会超时。
优化1:在递推的过程中,如果步数已经小于已有的最小答案了,那么久不需要继续递推下去了。
优化2:当 r < c 的时候,我们可以把 r 和 c 翻转,这样所耗时间可以少很多(具体少多少我也不清楚,可以自己记下时对比一下)
下面贴上AC代码:
(ps:比赛现场写的代码,很多地方不够简洁,累赘或者混乱,但是大致思路是没问题的,还请大家多多指教~)
#include
#include
#include
#include
using namespace std;
int maps[20];
int kk[20];
void change(int r,int n,int p,int c);
int main()
{
int c,r,n;
char m[20][20];
while(~scanf("%d%d",&r,&c))
{
if(r==0||c==0)
{
break;
}
getchar();
if(r>=c)
{
for(int i=1; i<=r; i++)
{
maps[i]=0;
scanf("%s",m[i]);
for(int j=0; j0)
{
ans++;
}
}
for(int j=1; j<=r; j++)
{
kk[j]=maps[j];
}
change(1,i,r,c);
//printf("%d\n",i);
for(int j=2; j<=r; j++)
{
//printf("%d\n",kk[j-1]);
for(int l=0; l0)
{
ans++;
}
}
change(j,kk[j-1],r,c);
if(ans>=minans)//优化1:如果已经大于最小的答案,那么就不用继续了
{
//printf("no\n");
//printf("\n");
break;
}
}
if(kk[r]==0&&ans0)
{
//注意判断越界!
if(r>1)
{
kk[r-1]=kk[r]^(1<0)
{
kk[r]=kk[r]^(1<<(i-1));//翻转此行左一位
}
if(i
到此为止,欢迎交流。
最后再附一张女神照~
10101
11011