好久没做过这么恶心的DP题了

题面

题面很简单,有一个计算式,由+号、*号、括号和小于10的正整数组成,现在所有的+*(由于属于违禁词而)都被-号给和谐掉了,现在要求所有可能的原计算式的结果之和。

你知道的信息:计算式总长度

n

[

1

,

1

0

5

]

n\in[1,10^5]

n∈[1,105](其中保证-号总数

m

2500

m\leq2500

m≤2500),原计算式的+号总数

k

[

0

,

m

]

k\in[0,m]

k∈[0,m] ,被和谐后的计算式(含括号)。

题解

括号表示了计算间的优先关系,我们可以通过这种关系建棵树,子节点比父节点先算。

然后,设计DP状态:

d

p

[

i

]

[

j

]

.

s

u

m

dp[i][j].sum

dp[i][j].sum 表示该子树

i

i

i 内存在

j

j

j 个+号的所有算式结果之和,

d

p

[

i

]

[

j

]

.

c

n

t

dp[i][j].cnt

dp[i][j].cnt 表示该子树

i

i

i 内存在

j

j

j 个+号的算式总数。此处

d

p

[

i

]

[

j

]

dp[i][j]

dp[i][j] 是一个二元组。

经典的树形背包DP枚举+转移思路:记录前面儿子的答案,与下一个儿子合并。此时“前面儿子”不一定两端有括号,但下一个儿子一定是一个整体。

那么对于两个算式间用+号相连(

C

=

A

+

B

C=A+B

C=A+B),有转移:

d

p

[

C

]

[

j

+

k

+

1

]

(

d

p

[

A

]

[

j

]

.

s

u

m

d

p

[

B

]

[

k

]

.

c

n

t

+

d

p

[

B

]

[

k

]

.

s

u

m

d

p

[

A

]

[

j

]

.

c

n

t

,

d

p

[

A

]

[

j

]

.

c

n

t

d

p

[

B

]

[

k

]

.

c

n

t

)

dp[C][j+k+1]\leftarrow (dp[A][j].sum*dp[B][k].cnt+dp[B][k].sum*dp[A][j].cnt~,\\dp[A][j].cnt*dp[B][k].cnt)

dp[C][j+k+1]←(dp[A][j].sum∗dp[B][k].cnt+dp[B][k].sum∗dp[A][j].cnt ,dp[A][j].cnt∗dp[B][k].cnt)

但是对于乘法(

C

=

A

(

B

)

C=A*(B)

C=A∗(B))的情况就有困难,由于前一个算式不一定两端有括号,所以

B

B

B 只能乘

A

A

A 的最后一项。那我们就把

A

A

A 的所有情况下的最后一项拿出来求和,记为

g

[

A

]

[

.

.

.

]

g[A][...]

g[A][...](不是二元组),然后可以有一个复杂的转移:

d

p

[

C

]

[

j

+

k

]

(

d

p

[

B

]

[

k

]

.

s

u

m

g

[

A

]

[

j

]

+

(

d

p

[

A

]

[

j

]

.

s

u

m

g

[

A

]

[

j

]

)

d

p

[

B

]

[

k

]

.

c

n

t

,

d

p

[

A

]

[

j

]

.

c

n

t

d

p

[

B

]

[

k

]

.

c

n

t

)

g

[

C

]

[

j

+

k

+

1

]

d

p

[

B

]

[

k

]

.

s

u

m

d

p

[

A

]

[

j

]

.

c

n

t

g

[

C

]

[

j

+

k

]

g

[

A

]

[

j

]

d

p

[

B

]

[

k

]

.

s

u

m

dp[C][j+k]\leftarrow \Big(dp[B][k].sum*g[A][j]+(dp[A][j].sum-g[A][j])*dp[B][k].cnt~,\\ dp[A][j].cnt*dp[B][k].cnt\Big)\\ g[C][j+k+1]\leftarrow dp[B][k].sum*dp[A][j].cnt\\ g[C][j+k]\leftarrow g[A][j]*dp[B][k].sum

dp[C][j+k]←(dp[B][k].sum∗g[A][j]+(dp[A][j].sum−g[A][j])∗dp[B][k].cnt ,dp[A][j].cnt∗dp[B][k].cnt)g[C][j+k+1]←dp[B][k].sum∗dp[A][j].cntg[C][j+k]←g[A][j]∗dp[B][k].sum

复杂度是经典的树上背包DP时间复杂度,

O

(

n

2

)

O(n^2)

O(n2) 。

有几点要注意的:

  1. n

    n

    n 很大,

    m

    m

    m 很小,说明括号可能很多,得缩掉一些儿子数只有1的废点。

  2. 注意转移的先后顺序。
  3. 注意子树 size ,边界情况卡准。
  4. 回溯的时候,由于在算式两边加上了括号,要把所有的

    g

    [

    i

    ]

    [

    j

    ]

    g[i][j]

    g[i][j] 赋值为

    d

    p

    [

    i

    ]

    [

    j

    ]

    .

    s

    u

    m

    dp[i][j].sum

    dp[i][j].sum。

CODE

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 2505
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
LL read() {
LL f=1,x=0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f*x;
}
void putpos(LL x) {
if(!x) return ;
putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
if(!x) putchar('0');
else if(x < 0) putchar('-'),putpos(-x);
else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);} const int MOD = 1000000007;
int n,m,s,o,k;
int le;
char ss[100005];
int cnd,sz[MAXN];
struct it{
int x,y;it(){x=y=0;}
it(int X,int Y){x=X;y=Y;}
};
it operator + (it a,it b) {return it((a.x+b.x)%MOD,(a.y+b.y)%MOD);}
it Plus(it a,it b) {return it((a.x*1ll*b.y%MOD+a.y*1ll*b.x%MOD)%MOD,a.y*1ll*b.y%MOD);}
it Mult(it a,it b) {return it(a.x*1ll*b.x%MOD,a.y*1ll*b.y%MOD);}
it dp[MAXN][MAXN];
int g[MAXN][MAXN];
int dfs(int ad) {
if(ss[ad] != '(') {
int nm = ss[ad]-'0';
int x = ++ cnd;
sz[x] = 1;
for(int i = 1;i <= m;i ++) dp[x][i] = it(),g[x][i] = 0;
dp[x][0] = it(nm,1);
g[x][0] = nm;
return x;
}
int le = 0,cc = 1,st = ad;
vector<int> v;
v.push_back(0);
while(cc) {
ad ++;
if(ss[ad] != '-') {
if(ss[ad] == ')') cc --;
else {
if(cc == 1) v.push_back(dfs(ad)),le ++;
if(ss[ad] == '(') cc ++;
}
}
}
int tl = cnd+1;
int siz = sz[v[1]],las = v[1];
for(int i = 2;i <= le;i ++) {
int y = v[i],p = v[i-1];
las = y;
siz += sz[y];
for(int j = 0;j < siz;j ++) dp[tl][j] = it(),g[tl][j] = 0;
for(int j = 0;j < sz[y];j ++) {
for(int k = 0;k < siz-sz[y];k ++) {
int nm = (dp[y][j].x *1ll* g[p][k] % MOD + (dp[p][k].x+MOD-g[p][k]) % MOD *1ll* dp[y][j].y % MOD) % MOD;
dp[tl][j+k] = dp[tl][j+k] + it(nm,dp[y][j].y *1ll* dp[p][k].y % MOD);
dp[tl][j+k+1] = dp[tl][j+k+1] + Plus(dp[y][j],dp[p][k]);
(g[tl][j+k] += g[p][k] *1ll* dp[y][j].x % MOD) %= MOD;
(g[tl][j+k+1] += dp[y][j].x *1ll* dp[p][k].y % MOD) %= MOD;
}
}
swap(dp[tl],dp[y]);
swap(g[tl],g[y]);
sz[y] = siz;
}
for(int i = 0;i < siz;i ++) g[las][i] = dp[las][i].x;
return las;
}
int main() {
freopen("operator.in","r",stdin);
freopen("operator.out","w",stdout);
le = read();m = read();
scanf("%s",ss + 1);
ss[0] = '(';
ss[le+1] = ')';
int rt = dfs(0);
// printf("\n<%d>\n",n);
AIput(dp[rt][m].x,'\n');
return 0;
}

【碳硫磷模拟赛】消失的+和* (树形DP)的更多相关文章

  1. codehunter 「Adera 6」杯省选模拟赛 网络升级 【树形dp】

    直接抄ppt好了--来自lyd 注意只用对根判断是否哟留下儿子 #include<iostream> #include<cstdio> using namespace std; ...

  2. 6.28 NOI模拟赛 好题 状压dp 随机化

    算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...

  3. 「模拟赛20190327」 第二题 DP+决策单调性优化

    题目描述 小火车虽然很穷,但是他还是得送礼物给妹子,所以他前往了二次元寻找不需要钱的礼物. 小火车准备玩玩二次元的游戏,游戏当然是在一个二维网格中展开的,网格大小是\(n\times m\)的,某些格 ...

  4. 模拟赛20181015 Uva1078 bfs+四维dp

    题意:一张网格图,多组数据,输入n,m,sx,sy,tx,ty大小,起终点 接下来共有2n-1行,奇数行有m-1个数,表示横向的边权,偶数行有m个数,表示纵向的边权 样例输入: 4  4  1  1  ...

  5. 【noip模拟赛7】上网 线性dp

    描述 假设有n个人要上网,却只有1台电脑可以上网.上网的时间是从1 szw 至 T szw ,szw是sxc,zsx,wl自创的时间单位,至于 szw怎么换算成s,min或h,没有人清楚.依次给出每个 ...

  6. 【noip模拟赛5】任务分配 降维dp

    描述 现有n个任务,要交给A和B完成.每个任务给A或给B完成,所需的时间分别为ai和bi.问他们完成所有的任务至少要多少时间. 输入 第一行一个正整数n,表示有n个任务.接下来有n行,每行两个正整数a ...

  7. 2015年第六届蓝桥杯省赛T10 生命之树(树形dp+Java模拟vector)

    生命之树 在X森林里,上帝创建了生命之树. 他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值. 上帝要在这棵树内选出一个非空节点集S,使得对于S中的任意两个点a,b,都 ...

  8. (计数器)NOIP模拟赛(神奇的数位DP题。。)

    没有原题传送门.. 手打原题QAQ [问题描述]     一本书的页数为N,页码从1开始编起,请你求出全部页码中,用了多少个0,1,2,…,9.其中—个页码不含多余的0,如N=1234时第5页不是00 ...

  9. 「模拟赛20191019」B 容斥原理+DP计数

    题目描述 将\(n\times n\)的网格黑白染色,使得不存在任意一行.任意一列.任意一条大对角线的所有格子同色,求方案数对\(998244353\)取模的结果. 输入 一行一个整数\(n\). 输 ...

随机推荐

  1. 2021.06.05【NOIP提高B组】模拟 总结

    T1 题意:给你一个 \(n\) 个点 \(n\) 条边的有向图, 求每个店经过 \(K\) 条边后的边权和.最小边权 \(K\le 10^{10}\) 考试时:一直想着环,结果一直不知道怎么做 正解 ...

  2. synchronized下的 i+=2 和 i++ i++执行结果居然不一样

    起因 逛[博客园-博问]时发现了一段有意思的问题: 问题链接:https://q.cnblogs.com/q/140032/ 这段代码是这样的: import java.util.concurrent ...

  3. 方法(method)

    方法是可以完成某个特定的功能,并且可以重复利用的代码片段...C中叫为函数 方法定义在类体中,不可定义在主方法下. 一个方法执行完就会被释放, 提高代码的复用性 相同的业务逻辑就可以不用重复,,,,因 ...

  4. Spring Security认证器实现

    目录 拦截请求 验证过程 返回完整的Authentication 收尾工作 结论 一些权限框架一般都包含认证器和决策器,前者处理登陆验证,后者处理访问资源的控制 Spring Security的登陆请 ...

  5. ThreadLocal的简单理解

    目录 一.背景 二.ThreadLocal解决的问题 三.如何创建一个ThreadLocal实例 四.ThreadLocal如何做到线程变量隔离 1.理解3个类 2.看下set方法是如何实现的 3.看 ...

  6. 无语——真的好用到不行的7个Python小技巧

    本文总结了我几个我在学习python过程中,用到的几个超好用的操作,这里分享给大家,我相信你们也会非常喜欢,目录如下.这里提前索要再看,记得点一点再看哦.这只是其中一些技巧,以后会慢慢和大家分享. 1 ...

  7. Python快速下载商品数据,并连接数据库,保存数据

    开发环境 python 3.8 pycharm 2021.2 专业版 代码实现 发送请求 获取数据 解析数据(筛选数据) 保存数据 连接数据库 开始代码 请求数据 # 伪装 headers = { ' ...

  8. python写个前端,这不是轻轻松松~

    前端除了用js++css+html,还有没有其它办法?其实python也可以 1. 安装与基本流程 Python学习交流Q群:660193417### 安装 PyWebIO 和其他的第三方库一样使用p ...

  9. 719. 找出第 K 小的数对距离

    719. 找出第 K 小的数对距离 这道题其实有那么一点二分猜答案的意思,也有很多类似题目,只不过这道题确实表达的不是很清晰不容易想到,题没问题,我的问题.既然是猜答案,那么二分边界自然就是距离最大值 ...

  10. RocketMQ 集群的搭建部署 以及rocketmq-console-ng仪表台的安装部署

    在 RocketMQ 主要的组件如下. NameServerNameServer 集群,Topic 的路由注册中心,为客户端根据 Topic 提供路由服务,从而引导客户端向 Broker 发送消息.N ...