费解的开关

题目

题目

讲解

先将两个比较重要的性质:

  1. 对于一个按钮,不可能按两次,按两次就等于不按。
  2. 按按钮的顺序不会影响最终结果。(也就是无序)

至于为什么,可以结合位运算来思考,在此不多讲了。

仍然分成做法1和做法2。

做法1(我的做法)

没错,我的做法就是暴力,传统艺能。

为了节省空间,我把(1)设为灯是暗的,(0)是亮的。(实际上并没有节省)

然后就变成了我们要让图全部变成(0)了,但是我们发现这个操作是可逆的,如果(X)能花(5)步变成(Y),那(Y)也能花(5)步变成(X),重点是数据范围是(5*5)的,而(2^{25}-1=33554431),那么我们就可以直接用一个(int)来反映整个图的开关情况了,然后我们预处理出(0)状态到(X)状态所需要的步数就行了,对于超过(6)步的就不理他了。

当然我还有一个性质不知道怎么证的,只是觉得他是对的,但是不会证,但我也用了因为我找不出反例。(其实不用改成min就行了)
对于每个按钮,不可能通过按其他的按钮来实现这个按钮的功能,这意味着(0)到每个图所需要按的按钮是唯一的。

时间复杂度:(O(C_{25}^{6}+n))

#include<cstdio>
#include<cstring>
#define  N  41000000
using  namespace  std;
int  a[N];
inline  int  close(int  x/*状态*/,int  k/*位置*/)
{
	x^=(1<<(k-1));
	if(k%5!=0)x^=(1<<k);
	if(k%5!=1)x^=(1<<(k-2));
	if(k>5)x^=(1<<(k-6));
	if(k<=20)x^=(1<<(k+4));
	return  x;
}
void  dfs(int  k/*表示状态*/,int  t/*表示这次是第几次开灯了*/,int  x/*这是决定在哪里开灯的*/)
{
 	a[k]=t;//记录状态,这里就用到了那个神奇的性质了
	if(t==6  ||  x>25)return  ;//以后都不可能开灯了 
	dfs(close(k,x),t+1,x+1);//开灯 
	dfs(k,t,x+1);//不在这里开灯 
}
int  main()
{
	int  T;scanf("%d",&T);
	dfs(0,0,1);
	while(T--)
	{
		char  st[10];
		int  x=0;
		for(int  i=1;i<=5;i++)
		{
			scanf("%s",st+1);
			for(int  j=1;j<=5;j++)
			{
				if(st[j]=='0')x^=(1<<((i-1)*5+j-1));
			}
		}
		if(x==0)printf("0
");
		else  if(a[x]==0)printf("-1
");
		else  printf("%d
",a[x]);
	}
	return  0;
}

做法2

奆佬就是奆佬,惹不起惹不起。

果然,不出所料。

https://www.acwing.com/solution/content/804/博客中奆佬给出了一种时间复杂度更好看的做法(当然这个做法更吃按钮方案唯一这个性质,但是我还是不会证QAQ,但是感觉是对的)。

这个做法要从我们看这个开关的角度来看了,开关是一个“十”字的构造,题目中是说点中间影响四周,但是我们可以看成我们点了上面的开关,影响了下面四个开关,这样子看的话,我们点开关的时候就可以一层层向下推进了。

但是这样存在两个问题。

  1. 不是最少的点击次数,例如:
    000
    101
    111
    如果按照这种方法按的次数是远大于(3)的,而且题目中都说了我们按了中间影响了四周,所以我们必须要考虑第一行是中间的情况,对于这种情况,奆佬选择了直接暴力枚举操作序列,比如我枚举到了(00110),说明我会按3、4号开关,但是这会花费(2^5)的代价足足32啊,四舍五入就是一个亿啊,然后再按这个做法做,就解决了这种情况。

  2. 不能完美的把图变成(0)
    如:
    000
    101
    111
    我们用我们的方法处理第一行:
    111
    111
    000
    第二行不用处理了,但到了第(3)行,我们就不能用这个方法了,因为我们其实只是把按按钮换了个角度看,实际位置并没变,如果我们按那个方法接着做,那么按开关的实际位置就是在第(4)行了。
    而且这也恰巧为按钮方案唯一这个提供了有力的证据,第一个问题是不是最少的点击次数,但是根据这个性质理论上来讲只存在一个点击次数啊,所以这个问题就说明了,这种方式构造出的方案不仅点击次数多,而且还是错误。
    至于解决的方法,就是不让最后一行参与到这个方法中,同时在做完之后看看图是否满足要求就行了。(其实只用检查最后一行)

时间复杂度:(O(2^5*5^2*n))

代码采用https://www.acwing.com/solution/content/804/:

#include <bits/stdc++.h>
using namespace std;
int n,m,i,j,k,a[7][7],ans1=1e6,b[7][7];//7,7是因为怕在最后一排溢出
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        getchar();
        for (i=1;i<=5;i++)
        {
            for (j=1;j<=5;j++)
            {
                char ch=getchar();
                b[i][j]=ch-'0';
            }
            getchar();
        }
        for (i=0;i<=(1<<5);i++)//模拟第一行的操作序列
        {
            for (j=1;j<=5;j++)
            {
                for (k=1;k<=5;k++)
                    a[j][k]=b[j][k];
            }
            int ans=0;
            for (j=1;j<=5;j++)
                if (i>>(j-1) & 1)
                {
                    ans++;
                    a[1][j-1]^=1;
                    a[1][j+1]^=1;
                    a[1][j]^=1;
                    a[2][j]^=1;
                }
            for (j=1;j<=4;j++)//切记是1~4,而不是2~5,因为我们是控制i+1行,而不是控制第i行
                for (k=5;k>=1;k--)
                    if (!a[j][k])
                    {
                        ans++;
                        a[j][k]^=1;//上面
                        a[j+2][k]^=1;//下面
                        a[j+1][k]^=1;//本身
                        a[j+1][k+1]^=1;//右面
                        a[j+1][k-1]^=1;//左面
                    }
            //cout<<ans<<endl;
            bool ok=true;
            for (j=1;j<=5;j++)
                for (k=1;k<=5;k++)
                    if (!a[j][k])
                        ok=false;
            if (ok)
                ans1=min(ans1,ans);//,cout<<ans<<endl;
        }
        if (ans1>6)
            cout<<-1;
        else
            cout<<ans1;
        ans1=1e7;
        puts("");
    }
    return 0;
}

原文地址:https://www.cnblogs.com/zhangjianjunab/p/13391276.html