【NOI2014】起床困难综合症 解题报告

来源:互联网 发布:织梦cms邀请码 编辑:程序博客网 时间:2024/05/02 04:32

先说一下这道题对于我的历史意义:这是我做出的第一道NOI的题目,而且,全程是自己思考,没有看别人的程序或者想法。

题目描述

21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。通过研究相关文献,他找到了该病的发病原因:在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。正是由于 drd 的活动,起床困难综合症愈演愈烈,以惊人的速度在世界上传播。为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。
历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。具体说来,drd 的防御战线由 nn 扇防御门组成。每扇防御门包括一个运算 opop 和一个参数 tt,其中运算一定是 OR,XOR,ANDOR,XOR,AND 中的一种,参数则一定为非负整数。如果还未通过防御门时攻击力为 xx,则其通过这扇防御门后攻击力将变为 x op tx op t。最终drd 受到的伤害为对方初始攻击力 xx 依次经过所有 nn 扇防御门后转变得到的攻击力。
由于 atm 水平有限,他的初始攻击力只能为 00 到 mm 之间的一个整数(即他的初始攻击力只能在 0,1,…,m0,1,…,m 中任选,但在通过防御门之后的攻击力不受 mm 的限制)。为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd 受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。

输入格式

第一行包含两个整数,依次为 n,mn,m,表示 drd 有 nn 扇防御门,atm 的初始攻击力为 00 到 mm 之间的整数。
接下来 nn 行,依次表示每一扇防御门。每行包括一个字符串 opop 和一个非负整数 tt,两者由一个空格隔开,且 opop 在前,tt 在后,opop 表示该防御门所对应的操作,tt 表示对应的参数。

输出格式

一行一个整数,表示 atm 的一次攻击最多使 drd 受到多少伤害。

样例一

input

3 10
AND 5
OR 6
XOR 7

output

1

分析:一种神奇的贪心

首先,既然是一大堆^&|啊什么什么的东西,那么自然要想到转二进制乱搞了~
(考虑到是二进制,那么下面的陈述一律使用二进制下)
所有0~m的伤害,每一个经过东搞西搞的转化后,都会有一个特点:
令当前输出的初始伤害有len[0]位,从1~n的转换值有len[i]位(注意是二进制),那么,最终这个转换后的伤害的长度应该是max(len) 。
其实,所有len[0]的最大值就是m的位数,那么,所有转换后伤害的长度的最大值显然了。

为什么要考虑数位长度呢?
因为这是可以用贪心的啊!答案每一位无非就是0或者1,所以,针对每一位,选一个数(0或者1)把答案的这一位尽量搞成1就行了(除非选0和1都不能让答案为1)。这样,一位一位拼凑出来的答案一定就是最优解了!

等等!如果我的选择是初始输出101110,但是我的m限制确实100110怎么办!
也就是说,上述算法有可能会超出m的范围。

再考虑一点:对于二进制数A=1XX……,所有的0XX……都是小于A的(后面那个XX……表示随便什么什么都可以,但是前后两个是一样的)。

那么,设m有len位。我们的答案就只需要后面len-1位找就起码可以保证不会超出范围了(也就是强制把m的最高位看做0)。
但还是会留下一部分数啊,怎么办?
显然,假设选取的初始输出的最高位就是1,然后再往后在限制条件里找。

限制条件是什么呢?
如果当前m的这一位是0,那么我选取数的这一位就只可能是0。(这很显然)
如果当前m的这一位是1,那么我就可以选择1或0。
根据贪心法,我们的选择是选收益高的(谁能把答案的这一位搞成1就选谁),还有一个要注意到的地方,如果在1的限制下选1或0把答案都是搞成相同的数,一定选0(因为后面的数可以随意选了,更有“前途”原因见A=1XX……那里)。

最后,再把初始输出最高位选1和0得出的两个答案取最大,就是最终结果了。

<—-以上就是我的思考过程,有木有懂了呢?
然后附代码:

#include <iostream>#include <cstdlib>#include <cstdio>#include <cstring>#include <cmath>using namespace std ;const int maxn = 1e5+10, maxm = 31 ;struct op{ //记录每一次操作详情 operation    char d ; //哪一种运算 doing    int val ; //一开始是存储运算的值(value),后来为了毫无意义的节省空间,又换了个用处    bool v[maxm] ; //二进制存储运算的值(value)} o[maxn] ; //数组名称的意思是operationbool ok[2][maxm] ; //ok[i][j]用来记录初始输出第j位是i时,获得的最终结果char cha[10] ; //用来输入而已int n, m, max_len = -1 ; //max_len就是上述的最大长度void dec ( int x ) { //分解    int len = 0, val = o[x].val ;    while (val) {        o[x].v[++len] = val&1 ;        val >>= 1 ;    }    o[x].val = len ;}bool limit[40] ; //m产生的每一位的限制(二进制)void Pre() { //预处理出ok数组    int i, j, k ;    for ( k = 1 ; k <= max_len ; k ++ ) //枚举长度        for ( j = 0 ; j < 2 ; j ++ ) { //选取的数            bool ans = j ;            for ( i = 1 ; i <= n ; i ++ ) { //一共进行n次变化                if ( o[i].d == 'A' ) ans &= o[i].v[k] ;                else if ( o[i].d == 'O' ) ans |= o[i].v[k] ;                else if ( o[i].d == 'X' ) ans ^= o[i].v[k] ;            }            ok[j][k] = ans ;            }}int main() {    int i, j, k, len = 0 ;    scanf ( "%d%d", &n, &m ) ;    for ( i = 1 ; i <= n ; i ++ ) {         scanf ( "%s %d", cha, &o[i].val ) ;        o[i].d = cha[0] ; //由于每个开头字母就不一样,存第一个就够了    }    for ( i = 1 ; i <= n ; i ++ ) { //依次分解并记录最大长度        dec(i) ;         if ( o[i].val > max_len ) max_len = o[i].val ;    }    while (m) { //计算limit[]        limit[++len] = m&1 ;        m >>= 1 ;    }    max_len = max ( len, max_len ) ;//别忘了m的长度    Pre() ; //预处理ok[][]    int ans1, ans2 ;    //ans1是存储假设最高位不是1的答案    ans1 = ans2 = 0 ;    //ans2是存储最高位是1的答案    for ( i = len-1 ; i > 0 ; i -- )         ans1 += ( max(ok[0][i],ok[1][i])<<i-1 ) ;    //无脑计算ans1    bool is_lim = true ; //表示是否受到限制    for ( i = max_len ; i ; i -- )         if ( is_lim ) { //略有麻烦,自己看看就能懂            if ( !limit[i] ) ans2 += ( ok[0][i]<<i-1 ) ;            else {                if ( ok[0][i] >= ok[1][i] ) {                    is_lim = false ;                    ans2 += ( ok[0][i]<<i-1 ) ;                } else ans2 += ( ok[1][i]<<i-1 ) ;            }        } else ans2 += ( max(ok[0][i],ok[1][i])<<i-1 ) ; //如果已经可以随便选,就无脑乱搞    printf ( "%d\n", max ( ans1, ans2 ) ) ; //选取最优    return 0 ;}
1 0