[正睿OJ 270] 关灯 解题报告 (倒着DP)

题目链接:http://zhengruioi.com/contest/70/problem/270

夜色已晚,九条可怜要睡觉了,于是她让她的管家来把房间里的灯都关了。

可怜的房间里有 n 盏灯,编号为 1 到 n,最开始其中一些是亮的,一些是灭的。从第 1 时刻开始,管家每一时刻可以选择一盏灯按下开关(当然也可以选择不按):如果原来这盏灯是亮的,那么在按下开关后会熄灭,否则会被点亮。

可怜喜欢新奇的小玩意,自然房间里的灯和普通的灯都不一样:如果管家在第 i 时刻按下了第 j 盏灯,那么对于所有正整数 k[1,nj],在第 i+k时刻,第 j+k 盏灯的开关会被自动按下一次。

举例来说,如果 n=4且管家在前 3 个时刻分别按下了第 1,3,3盏灯的开关,那么实际上:

  1. 第一个时刻,第 1 盏灯的开关被按了一次。
  2. 第二个时刻,第 2,3 盏灯的开关分别被按了一次。
  3. 第三个时刻,第 3 盏灯的开关被按了两次(结果上来说第 3 盏灯的状态没有改变),第 4 盏灯的开关被按了一次。
  4. 第四个时刻,第 4 盏灯的开关被按了两次。

因此在第 4 个时刻结束的时候,所有灯的状态都被取反了。

因为九条可怜已经很累了,所以只有有一个时刻,所有灯都是灭的,那么可怜就会马上睡着(即使在下一时刻又会有灯亮起来)。现在管家想要知道最优情况下可怜能在第几时刻入睡。

输入格式

输入一行一个 01 字符串 s ,其中 |s| 表示灯的个数。第 i 个字符如果是 1 表示初始时这一盏灯是亮着的,否则表示这一盏灯是灭的。

输出格式

输出一行一个整数表示答案:可怜最早的入睡时刻。

样例1

样例输入1

0010

样例输出1

1

样例2

样例输入2

0

样例输出2

0

样例3

样例输入3

111

样例输出3

2

限制与约定

本题采用捆绑测试的方式,有如下 3 个子任务:

  1. n9 并保证最优解小于等于 9。分值 30 分。
  2. n17。分值 30 分。
  3. n20。分值 40 分。

时间限制:1s

空间限制:512MB

题解:

我们考虑DP,倒着的那种(倒着指的是时间上的倒着)

考虑最终睡着的时刻k,假如我们在第k-1的时刻对某盏灯l操作,l到l+1这段区间会被翻转

那么我们继续倒着往回,最终回到时刻1,总长度为k也就是我们求的值

这样的话我们就可以倒着DP了,dp[i][j]表示是否存在状态,后i个时刻,灯的状态为j。当我们发现存在dp[i][0]说明后i个时刻可以把灯给关上,那么时刻总长度就是i,也就是前i个时刻可以把灯关上

转移的话就是枚举翻转的位置l,将l~l+i-1这一段区间翻转即可

代码如下:

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

string s;
int num,n;
int dp[21][2000010];
int reverse(int l,int r)
{
    return ((1<<r)-1)-((1<<l-1)-1);
}
int main()
{
    cin>>s;
    n=s.size();
    for (int i=0;i<n;i++) num+=(s[i]-'0')*(1 << i);
    if (!num) {puts("0");return 0;}
    dp[0][num]=1;
    for (int i=1;i<=n;i++)
    {
        for (int s=0;s<(1<<n);s++)
        {
            if (!dp[i-1][s]) continue;
            for (int j=0;j<=n;j++)
            {
                if (j==0) dp[i][s]|=dp[i-1][s];
                else dp[i][s^(reverse(j,min(j+i-1,n)))]|=dp[i-1][s];
            }
        }
        if (dp[i][0])
        {
            printf("%d\n",i);
            return 0;
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xxzh/p/9463529.html