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\)为根的子树中国使用了\(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));
}

过了一个假期颓成狗了... 代码都不会写了快...

啊啊啊啊啊下午还要测试怎么办啊~

原文地址:https://www.cnblogs.com/enzymii/p/8458397.html

时间: 2024-08-30 18:35:57

CF935E Fafa and Ancient Mathematics 树形dp的相关文章

CF935D Fafa and Ancient Alphabet 概率dp(递推)

D. Fafa and Ancient Alphabet (简洁题意请往下翻) time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output Ancient Egyptians are known to have used a large set of symbols  to write on the walls of the temple

Codeforces 935E Fafa and Ancient Mathematics(表达式转树 + 树型DP)

题目链接  Codeforces Round #465 (Div. 2) Problem E 题意  给定一个表达式,然后用$P$个加号和$M$个减号填充所有的问号(保证问号个数等于$P + M$) 求可以形成的表达式的最大值. 先把表达式转成一棵树,然后在树上DP. 题目保证了$min(P, M) <= 100$, 为了提高效率,我们选择用少的运算符号作为DP的第二维. 对$P$和$M$的大小关系进行分类讨论. 当$P < M$时, 设$f[i][j]$表示$i$代表的子树里面填$j$个加号

CF 337D Book of Evil 树形DP 好题

Paladin Manao caught the trail of the ancient Book of Evil in a swampy area. This area contains n settlements numbered from 1 to n. Moving through the swamp is very difficult, so people tramped exactly n - 1 paths. Each of these paths connects some p

HDU-2196 Computer (树形DP)

最近在看树形DP,这题应该是树形DP的经典题了,写完以后还是有点感觉的.之后看了discuss可以用树分治来做,以后再试一试. 题目大意 找到带权树上离每个点的最远点.︿( ̄︶ ̄)︿ 题解: 对于每一个点的最远点,就是以这个点为根到所有叶子节点的最长距离.但是如果确定根的话,除了根节点外,只能找到每个节点(度数-1)个子树的最大值,剩下一个子树是该节点当前的父亲节点. 所以当前节点的最远点在当前节点子树的所有叶子节点以及父亲节点的最远点上(当父亲节点的最远点不在当前节点的子树上时), 如果父亲节

UVA-01220 Party at Hali-Bula (树形DP+map)

题目链接:https://vjudge.net/problem/UVA-1220 思路: 树形DP模板题,求最大人数很简单,难点在于如何判断最大人数的名单是否有不同的情况: 解决方法是用一个数组f[manx][2]记录该节点是否出场的情况,为真时代表有多种情况; 具体讨论: 当父节点的值加上某个子节点的值时,他的f的情况也和该子节点一样: 当某个节点dp(i, 0) == dp(i, 1), 则该节点以及它的父节点也一定有多种情况(父节点必定取其中之一). Code: 1 #include<bi

HDU 1520 树形dp裸题

1.HDU 1520  Anniversary party 2.总结:第一道树形dp,有点纠结 题意:公司聚会,员工与直接上司不能同时来,求最大权值和 #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<cstdio> #define max(a,b) a>b?a:b using nam

HDU2196 Computer(树形DP)

和LightOJ1257一样,之前我用了树分治写了.其实原来这题是道经典的树形DP,感觉这个DP不简单.. dp[0][u]表示以u为根的子树中的结点与u的最远距离 dp[1][u]表示以u为根的子树中的结点与u的次远距离 这两个可以一遍dfs通过儿子结点转移得到.显然dp[0][u]就是u的一个可能的答案,即u往下走的最远距离,还缺一部分就是u往上走的最远距离: dp[2][u]表示u往上走的最远距离 对于这个的转移,分两种情况,是这样的: dp[2][v] = max( dp[0][u]+w

hdu5593--ZYB&#39;s Tree(树形dp)

问题描述 ZYB有一颗N个节点的树,现在他希望你对于每一个点,求出离每个点距离不超过KK的点的个数. 两个点(x,y)在树上的距离定义为两个点树上最短路径经过的边数, 为了节约读入和输出的时间,我们采用如下方式进行读入输出: 读入:读入两个数A,B,令fai??为节点i的父亲,fa?1??=0;fa?i??=(A∗i+B)%(i−1)+1,i∈[2,N] . 输出:输出时只需输出N个点的答案的xor和即可. 输入描述 第一行一个整数TT表示数据组数. 接下来每组数据: 一行四个正整数N,K,A,

CF 219D Choosing Capital for Treeland 树形DP 好题

一个国家,有n座城市,编号为1~n,有n-1条有向边 如果不考虑边的有向性,这n个城市刚好构成一棵树 现在国王要在这n个城市中选择一个作为首都 要求:从首都可以到达这个国家的任何一个城市(边是有向的) 所以一个城市作为首都,可能会有若干边需要改变方向 现在问,选择哪些城市作为首都,需要改变方向的边最少. 输出最少需要改变方向的边数 输出可以作为首都的编号 树形DP 先假定城市1作为首都 令tree(i)表示以i为根的子树 dp[i]表示在tree(i)中,若以i为首都的话,需要改变的边数 第一次