CF935E Fafa and Ancient Mathematics 樹形dp
前言
這是一道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\)
然後我們就記憶化搜索一波, 枚舉\(+\)的個數做就行了, 對於當前節點:
這個節點是個數字? 直接返回咯~
兩個兒子都是數字? 直接算咯~
填\(+\):
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