机房测试13:dp专题(单调队列+树形背包+记忆化搜索)

T1:

很容易写出dp式子:定义dp[i][j]为现在是第i个烟火,位置在j,然后就可以枚举上一个时间的位置k转移过来。(j-(t[i]-t[i-1])*d <= k <=j+(t[i]-t[i-1])*d)

这样是n*n*m的,考虑优化。

固定一个边界:j-(t[i]-t[i-1])*d<=k

可以发现,当j变大,k的范围会变小,于是就可以维护一个单调递减的队列,队头就是答案。

而另外一个边界同理,只需要反过来做一遍就可以了。

注意:

1. 开滚动数组!!否则爆空间

2.不需要初始化负无穷,因为dp是直接从1开始更新的。

#include<bits/stdc++.h>
using namespace std;
#define N 305
#define M 150005
#define ll long long
#define ri register int
int m,a[N];
ll n,d,b[N],t[N],dp[2][M],q[M];
int read()
{
    int x=0,fl=1; char ch=getchar();
    while(ch<‘0‘||ch>‘9‘) { if(x==‘-‘) fl=-1; ch=getchar(); }
    while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar();
    return x*fl;
}
int main()
{
    freopen("fireworks.in", "r", stdin);
    freopen("fireworks.out", "w", stdout);
    n=read(); m=read(); d=read();
    for(ri i=1;i<=m;++i) a[i]=read(),b[i]=read(),t[i]=read();
    //for(ri i=0;i<=1;++i) for(ri j=0;j<=n;++j) dp[i][j]=-(1ll<<60);
    //for(ri i=1;i<=n;++i) dp[1][i]=b[1]-abs(i-a[1]);
    int now=0,pre=0;
    for(ri i=1;i<=m;++i){
        pre=now,now^=1;
        //printf("%d %d\n",i,now);
        ll move=d*(t[i]-t[i-1]);
        if(move>n) move=n;
        int h=1,t=0;
        for(ri j=1;j<=n;++j){//正着考虑的是从j前面转移过来的
            while(h<=t && q[h]+move<j) ++h;
            while(h<=t && dp[pre][j]>=dp[pre][q[t]]) --t;
            q[++t]=j;
            dp[now][j]=dp[pre][q[h]] + b[i]-abs(j-a[i]);
        }
        h=1,t=0;
        for(ri j=n;j>=1;--j){//反着考虑的是从j后面转移过来的
            while(h<=t && q[h]-move>j) ++h;
            while(h<=t && dp[pre][j]>=dp[pre][q[t]]) --t;
            q[++t]=j;
            dp[now][j]=max(dp[now][j],dp[pre][q[h]] + b[i]-abs(j-a[i]));
        }
    }
    ll ans=-(1ll<<60);
    for(ri i=1;i<=n;++i) ans=max(ans,dp[now][i]);
    printf("%lld\n",ans);

}
/*
10 2 1
1 1000 4
9 1000 4

10 3 2
3 100 5
7 10 7
1 1 10 

10 6 2
6 1 5
7 1 7
3 1 10
3 29 11
6 5 100
2 7 101
*/

T2:

分析:

有依赖性的物品选择,就是树形背包。

如果成环,可以用tarjan缩点,如果是森林,可以连接超级源点。

这道题都不用,因为1<=xi<i,每个点向它前面的点连边,一定会成一棵树。

直接树形背包:

定义dp[i][j][0/1]为第i个节点,选了j的物品,子树中有没有用优惠券的最小花费。

但是这道题很坑的是,空间是256MB,直接开long long会爆空间,要针对题进行优化:

可见b最大只有1e9,dp大于b了,其实是没有用的,所以可以直接开int的dp数组,如果小于等于b就更新。

注意:

1.初始化要初始化全,要考虑到不选的情况。

2.当子树不用优惠券的时候,u这个根节点可以不选,不要漏了这种情况!!

#include<bits/stdc++.h>
using namespace std;
#define N 5005
#define ll long long
#define ri register int
ll c[N],d[N],b;
int dp[N][N][2],n,tot=0,to[N],nex[N],head[N],siz[N];
void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; }
void dfs(int u)
{
    dp[u][1][0]=c[u];
    dp[u][1][1]=c[u]-d[u];
    dp[u][0][0]=0;
    siz[u]=1;
    for(ri i=head[u];i;i=nex[i]){
        int v=to[i];
        dfs(v);
        /*for(ri j=mid;j>=1;--j)//没有优化的是n^3 siz优化可达n^2
         for(ri k=0;k<j;++k){
             dp[u][j][0]=min(dp[u][j][0],dp[u][j-k][0]+dp[v][k][0]);
             dp[u][j][1]=min(dp[u][j][1],dp[u][j-k][1]+ min(dp[v][k][0],dp[v][k][1]) );
        }*/
        for(ri j=siz[u];j>=0;--j)//这里j必须取到0 因为可以由根不选转移 这时候根不使用优惠券
        //j取0 为什么不会错呢?
        //考虑转移式子:tmp=dp[u][0][1] + (ll)min(dp[v][1][1],dp[v][1][0]) dp[u][0][1]是没有意义的 为正无穷 所以不会错
         for(ri k=1;k<=siz[v];++k){
             ll tmp=(ll) dp[u][j][0]+dp[v][k][0];
             if(tmp<=b) dp[u][j+k][0]=min(dp[u][j+k][0],(int)tmp);//
             tmp=(ll) dp[u][j][1] + (ll)min(dp[v][k][1],dp[v][k][0]);
             dp[u][j+k][1]=min(dp[u][j+k][1],(int)tmp);//
        }
        siz[u]+=siz[v];
    }
}
int main()
{
    freopen("shopping.in","r",stdin);
    freopen("shopping.out","w",stdout);
    scanf("%d%lld",&n,&b);
    scanf("%lld%lld",&c[1],&d[1]);
    int fa;
    for(ri i=2;i<=n;++i) scanf("%lld%lld%d",&c[i],&d[i],&fa),add(fa,i);
    for(ri i=0;i<=n;++i) for(ri j=0;j<=n;++j) for(ri k=0;k<=1;++k) dp[i][j][k]=1e9+1;
    dfs(1);
    int ans=0;
    for(ri i=n;i>=0;--i){
        if(dp[1][i][0] <= b || dp[1][i][1] <= b) { ans=i; break; }
    }
    printf("%d\n",ans);
}
/*
2 6
2 5
2 5 1

8 1000000000
400000000 300000000
800000000 300000000 1
200000000 100000000 1

400000000 200000000 2
700000000 200000000 2

300000000 200000000 2
700000000 300000000 5
200000000 100000000 3

8 11
4 3
8 3 1
2 1 1

4 2 2
7 2 2

3 2 2
7 3 5
2 1 3
*/

T3:

分析:

考虑怎么统计方案数:当一个区间有x种删法,除开这个区间外有y种删法,那么总的合法删法=x*y(乘法原理)

我们可以将大区间化成许多小区间,转换成子问题,分别统计贡献。

如果递归到了一个右括号,那么无论怎么删都是不合法的,直接跳过。

如果递归到了一个左括号,就给它匹配一个右括号,去计算这一个区间的贡献。

最后到了单点,return 1(因为只有一个括号且它是合法的,1的方案来源于不删它)

最后记得减去1(题中要求)

#include<bits/stdc++.h>
using namespace std;
#define N 305
#define ll long long
#define ri register int
const int mod = 1e9+7;
ll dp[N][N];
int n;
char s[N];
ll dfs(int l,int r)
{
    if(dp[l][r]!=-1) return dp[l][r];
    if(l>=r) return 1;
    ll res=dfs(l+1,r);
    if(s[l]==‘]‘||s[l]==‘)‘) return res;
    char tmp;
    if(s[l]==‘(‘) tmp=‘)‘;
    else tmp=‘]‘;
    for(ri i=l+1;i<=r;++i)
    if(s[i]==tmp) { res=(res+ dfs(l+1,i-1) * dfs(i+1,r) ) %mod;  }//break;
    return dp[l][r]=res;
}
int main()
{
    freopen("parenthesis.in","r",stdin);
    freopen("parenthesis.out","w",stdout);
    scanf("%d",&n);
    scanf("%s",s);
    memset(dp,-1,sizeof(dp));
    printf("%lld\n",dfs(0,n-1)-1);//不能打dp[0][n-1] 因为可能0位置是右括号 不合法 不能被更新到
}
/*
21
()[][]()[[]]][]()(())

8
()[][[]]
*/

原文地址:https://www.cnblogs.com/mowanying/p/11673078.html

时间: 2024-08-26 18:50:40

机房测试13:dp专题(单调队列+树形背包+记忆化搜索)的相关文章

【DP】树形DP 记忆化搜索

DP中的树形DP,解决方法往往是记忆化搜索.显然,树上递推是很困难的.当然做得时候还是得把状态定义和转移方程写出来:dp[u][1/0]表示以u为根节点的树 涂(1) 或 不涂(0) 颜色的最少方案数.树上DP有两个经典问法:一条边两端至少有个一个端点涂色,问整个tree最少涂色次数:还有一种忘了...此题是前种问法. #include<cstdio> #include<cstring> #include<algorithm> using namespace std;

Codeforces 509F Progress Monitoring (区间dp 或 记忆化搜索)

F. Progress Monitoring time limit per test 1 second memory limit per test 256 megabytes Programming teacher Dmitry Olegovich is going to propose the following task for one of his tests for students: You are given a tree T with n vertices, specified b

POJ 4968 DP||记忆化搜索

给出N个人的平局分X 根据GPA规则计算可能的最高平均GPA和最低平均GPA 可以DP预处理出来所有结果  或者记忆化搜索 DP: #include "stdio.h" #include "string.h" int inf=100000000; double a[11][1100],b[11][1100]; double Max(double a,double b) { if (a<b) return b; else return a; } double M

UVA - 10559 Blocks 和 Vasya and Binary String CodeForces - 1107E (dp OR 记忆化搜索)

UVA - 10559 Blocks 题意:消消乐,每次连续相同的可以消除,分数加上长度的平方,问最多可以获得几分全部消完 题解: 区间dp + 记忆化搜索 dp[i][j][k] : (区间 [i,  j] 后面带上一段和 j 颜色相同的且长度为 k )的消消乐最大积分 1.消最后一段颜色和 j 颜色相同的 dp[i][j][k] <-- dp[i][j-1][0] + (k+1)^2 2.对于i <= l < j, 如果 l 和 j 的颜色相同, 那么可以把 [l+1, j-1]消掉

[noi2013]快餐店 基环树dp,单调队列维护最大值和次大值

#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define N 220000 #define inf 0x3ffffffffffffffLL typedef long long ll; int v[N],e[N],ne[N],nn,w[N]; void add(int x,int y,int z){ ne[++nn

11782 - Optimal Cut(树形DP+记忆化搜索)

题目链接:11782 - Optimal Cut 题意:按前序遍历给定一棵满二叉树,现在有k次,可以选k个节点,获得他们的权值,有两个条件: 1.一个节点被选了,他的子节点就不能选了. 2.最终选完后,根到所有叶子的路径上,都要有一个被选的节点. 思路:树形dp,dp[u][k]代表在结点u,可以选k个节点,那么就分两种情况 选u节点,dp[u][k] = node[u]; 选子节点之和,那么就把k次分配给左右孩子,dp[u][k] = max(dp[u][k], dp[u][i], dp[u]

刷题总结——二叉苹果树(ssoj树形dp+记忆化搜索)

题目: 题目背景 URAL:http://acm.timus.ru/problem.aspx?space=1&num=1018 题目描述 有一棵苹果树,如果树枝有分叉,一定是分 2 叉(就是说没有只有 1 个儿子的结点,这棵树共有N 个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1. 我们用一根树枝两端连接的结点的编号来描述一根树枝的位置.下面是一颗有 4 个树枝的树:         2    5          \ /              3    4         

HDU 3652 B-number(数位dp&amp;记忆化搜索)

题目链接:[kuangbin带你飞]专题十五 数位DP G - B-number 题意 求1-n的范围里含有13且能被13整除的数字的个数. 思路 首先,了解这样一个式子:a%m == ((b%m)*c+d)%m; 式子的正确是显然的,就不证明了. 那么判断数是否可以被13整除就可以分解为一位一位进行处理. 当然,我们也只需要储存取余后的值. dfs(len, num, mod, flag) mod记录数字对13取余后的值 len表示当前位数 num==0 不含13且上一位不为1 pre==1

hdu1428 记忆化搜索(BFS预处理最短路径和+DP+DFS)

题意:有一块 n * n 大小的方形区域,要从左上角 (1,1)走到右下角(n,n),每个格子都有通过所需的时间,并且每次所走的下一格到终点的最短时间必须比当前格子走到重点的最短时间短,问一共有多少种走法. 这道题还是很明显的 DP 的,而且鉴于走到相邻格点可以上下左右走,所以我很快就锁定了记忆化搜索这种 DP 方式,但是事实上我的思路大方向的确没有错误,但是我仍然没有很好地挖掘题目的信息.我的想法是,某点到结尾的最短距离我可以转化成到起始点的最短距离,这样我就能从开头的点开始遍历,并且在遍历的