1. 程式人生 > >CF935E Fafa and Ancient Mathematics 樹形dp

CF935E Fafa and Ancient Mathematics 樹形dp

tro char 技術 得到 amp 一道 cnblogs 時間復雜度 技術分享

前言

這是一道cf的比賽題..

比賽的時候C題因為自己加了一個很顯然不對的特判WA了7次但找不出原因就棄療了...
然後就想劃水, 但是只做了AB又不太好... 估計rating會掉慘 (然而事實證明rating一點沒變)

就去看看別的題,, 但是英語不好, 看題要看半天, 看看這個E題題目名稱像是數論?(mmp估計是受到了古代豬文的影響). 點進去沒仔細讀題好像是個等價表達式一樣的題目? 好像很麻煩還1h不寫了(沒錯C題細節各種掛調了好久好久, 當時已經是很絕望了OvO)

結果這題tm是個dp...

題意

英文題一定要有的一個部分... 畢竟

這麽長時間不學, 還會說英語嗎? ——wcg

所以還是要翻譯一下...

就是給一個運算符都被扣掉的表達式, 讓你往裏面填\(P\)\(+\)\(M\)\(-\), 求最大的可能的結果.

表達式中的數字都是一位數, 而且每一層運算都套了一個括號, (這樣才比較方便處理, 其實麻煩一點也能處理但是...)

分析

顯然地, 我們可以把表達式畫成一棵樹. 以第四組樣例為例, 我們可以畫出一棵這樣的樹:

技術分享圖片

然後怎麽建樹啊, 我們知道這棵樹肯定是從底往上建的, 所以我們要利用一種神奇的, 叫"棧"的數據結構.

我們用一個臨時變量tmp來儲存等待著父親的左兒子. 這個左兒子可能是一個數, 也可能是一個點. 為了方便起見, 我們讓點的標號從11開始(因為數字只有一位...那你說為什麽不用10呢?).

  • 當我們掃到一個數字的時候, 把tmp設置為這個數字.
  • 當我們掃到一個?的時候, 我們建立一個新節點(其實就是++tot就行了), 將tmp作為他的左兒子, 右兒子先留空.
    然後將其入棧, 表示接下來的一個右兒子應該去找它.
  • 當我們遇到一個)的時候, 我們將tmp作為棧頂元素的右兒子. 然後將tmp設置為棧頂元素, 棧頂元素出棧.

發現自己並不能解釋清楚為什麽要這麽搞... 自己畫畫圖體會一下吧OvO.

建好樹後我們來設計狀態:

  • \(f[x][i]\)表示在以\(x\)為根的子樹中使用了\(i\)\(+\)得到的最大值
  • 由於有-的存在, 我們令\(g[x][i]\)表示在以\(x\)
    為根的子樹中國使用了\(i\)\(+\)得到的最小值

然後我們就記憶化搜索一波, 枚舉\(+\)的個數做就行了, 對於當前節點:

  • 這個節點是個數字? 直接返回咯~

  • 兩個兒子都是數字? 直接算咯~

  • \(+\):
    f[x][i]=max{f[lson[x]][j]+f[rson[x]][i-j-1]},j=0..i-1
    左右兩兒子都取最大時和最大
    g[x][i]=min{g[lson[x]][j]+g[rson[x]][i-j-1]}
    左右兩兒子都取最小時和最小

  • \(-\):
    f[x][i]=max{f[lson[x]][j]-g[rson[x]][i-j-1]}
    左兒子取最大, 右兒子取最小時差最大
    g[x][i]=min{g[lson[x]][j]-f[rson[x]][i-j-1]}

左兒子取最小, 右兒子取最大時差最小.

這樣就做完了(假的), 時間復雜度\(O(n*P)\), 可能會過不了.
而且空間復雜度也是\(O(n*P)\)的, 數組應該開不開..

但是呢\(min(P,M)\leq100\), 這樣我們就可以分類討論一下, 然後用上面的做法只枚舉較少的那個符號...

這樣時空復雜度就都能過辣...

代碼(寫的有點醜,沒怎麽壓行,calcMax和calcMin基本是一樣的...):

#include <cctype>
#include <cstdio>
#include <cstring>
const int INF=1000000007;
inline int max(const int &a,const int &b){return a>b?a:b;}
inline int min(const int &a,const int &b){return a<b?a:b;}
int t[5015][2],f[5005][102],g[5005][102],sz[5005];
int stk[5005],tp,cur,tot=10,rt;
char str[10010]; bool now;
void dfssz(int x){ //用子樹中包含運算符的個數來排除一部分不合法狀態.
    sz[x]=1;
    if(t[x][0]>10) dfssz(t[x][0]),sz[x]+=sz[t[x][0]];
    if(t[x][1]>10) dfssz(t[x][1]),sz[x]+=sz[t[x][1]];
}
void init(){ //建樹
    memset(f,192,sizeof(f));
    memset(g,127,sizeof(g));
    int l=strlen(str),fa;
    for(int i=0;i<l;++i){
        if(isdigit(str[i]))
            cur=str[i]-'0';
        if(str[i]=='?'){
            stk[++tp]=++tot;
            t[tot][0]=cur;
        }
        if(str[i]==')'){
            fa=stk[tp--];
            t[fa][1]=cur;
            cur=rt=fa;
        }
    }
    dfssz(rt);
}
int calcMax(int x,int p);
int calcMin(int x,int p){
    if(p<0||p>sz[x]) return INF;
    if(x<10) return x;
    if(sz[x]==1) return now==(bool)p?t[x][0]+t[x][1]:t[x][0]-t[x][1];
    if(g[x][p]<INF) return g[x][p];
    int mn=min(sz[t[x][0]],p),ans1,ans2;
    for(int i=0;i<=mn;++i){
        ans1=calcMin(t[x][0],i)+calcMin(t[x][1],p-now-i);   //+
        ans2=calcMin(t[x][0],i)-calcMax(t[x][1],p+now-1-i); //-
        g[x][p]=min(g[x][p],min(ans1,ans2));
    }
    return g[x][p];
}
int calcMax(int x,int p){   
    if(p<0||p>sz[x]) return -INF;
    if(x<10) return x;
    if(sz[x]==1) return now==(bool)p?t[x][0]+t[x][1]:t[x][0]-t[x][1];
    if(f[x][p]>-INF) return f[x][p];
    int mn=min(sz[t[x][0]],p),ans1,ans2;
    for(int i=0;i<=mn;++i){
        ans1=calcMax(t[x][0],i)+calcMax(t[x][1],p-now-i);   //+
        ans2=calcMax(t[x][0],i)-calcMin(t[x][1],p+now-1-i); //-
        f[x][p]=max(f[x][p],max(ans1,ans2));
    }
    return f[x][p];
}
int main(){
    scanf("%s",str);
    if(strlen(str)==1){puts(str);return 0;}
    init();
    int a,b; scanf("%d%d",&a,&b);
    if(a<b) now=1; else now=0; //now用來標記+多還是-多
    printf("%d",calcMax(rt,now?a:b));
}

過了一個假期頹成狗了... 代碼都不會寫了快...

啊啊啊啊啊下午還要測試怎麽辦啊~

CF935E Fafa and Ancient Mathematics 樹形dp