【NOI2014】起牀困難綜合症 解題報告

先說一下這道題對於我的歷史意義:這是我做出的第一道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] ; //數組名稱的意思是operation
bool 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 ;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章