• sss


    <更新提示>

    <第一次更新> 这一次组织了一场(dp)的专项考试,出了好几道经典的简单(dp)套路题,特开一篇博客写一下题解。


    <正文>

    Tower(双向dp)

    Description

    信大家都写过数字三角形问题,题目很简单求最大化一个三角形数塔从上往下走的路径和。走的规则是:(i,j)号点只能走向(i+1,j)或者(i+1,j+1)。如下图是一个数塔,映射到该数塔上行走的规则为:从左上角的点开始,向下走或向右下走直到最底层结束。

       1
    
       3 8
    
       2 5 0
    
       1 4 3 8
    
       1 4 2 5 0
    

    路径最大和是1+8+5+4+4 = 22,1+8+5+3+5 = 22或者1+8+0+8+5 = 22。

    小S觉得这个问题so easy。于是他提高了点难度,他每次ban掉一个点(即规定哪个点不能经过),然后询问你不走该点的最大路径和。

    当然他上一个询问被ban掉的点过一个询问会恢复(即每次他在原图的基础上ban掉一个点,而不是永久化的修改)。

    Input Format

    第一行包括两个正整数,N,M,分别表示数塔的高和询问次数。

    以下N行,第i行包括用空格隔开的i - 1个数,描述一个高为N的数塔。

    而后M行,每行包括两个数X,Y,表示第X行第Y列的数塔上的点被小S ban掉,无法通行。

    Output Format

    M行每行包括一个非负整数,表示在原图的基础上ban掉一个点后的最大路径和,如果被ban掉后不存在任意一条路径,则输出-1。

    Sample Input

    5 3
    1
    3 8
    2 5 0
    1 4 3 8
    1 4 2 5 0
    2 2
    5 4
    1 1
    

    Sample Output

    17
    22
    -1
    

    Limitation

    (nleq1000,mleq5*10^5)

    Solution

    这是经典的双向(dp),其思想在于对于一个特殊限定,我们可以在无限定的条件下先做两遍(dp),分别从起始状态和目标状态开始,然后在对特殊限定进行特殊处理,可以利用两遍(dp)得到的值进行快速的求解若干问题。

    那么在这道题中,我们分别需要做两遍(dp)(up_{i,j})代表从第一行到((i,j))位置的最大数值和,(down_{i,j})代表从最后一行到((i,j))位置的最大数值和。这两个(dp)都是非常容易解决的,其方程如下:$$up_{i,j}=max(up_{i-1,j},up_{i-1,j-1})+num_{i,j}down_{i,j}=max(down_{i+1,j},down_{i+1,j+1})+num_{i,j}$$

    然后我们预处理出每一行中使得(up_{i,j}+down_{i,j}-num_{i,j})取得最大值以及次大值的位置,那么对于一个损坏的位置((x,y)),若这个位置恰好在该行的最大值位置,显然全局的最大值就是该行的次大值,反之,全局最大值就是该行的最大值,那么我们就可以做到(O(1))回答每一个询问了。

    (Code:)

    #include <bits/stdc++.h>
    using namespace std;
    inline void read(long long &k)
    {
    	long long x=0,w=0;char ch;
    	while (!isdigit(ch)) 
    		w |= ch=='-' , ch = getchar();
    	while (isdigit(ch))
    		x = (x<<3) + (x<<1) + (ch^48) , ch=getchar();
    	k = ( w ? -x : x );
    } 
    const int N=1020;
    long long n,m,num[N][N],u[N][N],d[N][N];
    pair < long long , long long > pos[N];
    inline void input(void)
    {
    	read(n),read(m);
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=i;j++)
    			read(num[i][j]);
    }
    inline long long val(long long x,long long y)
    {
    	if ( !x || !y )return -1;
    	return u[x][y] + d[x][y] - num[x][y];
    }
    inline void dp(void)
    {
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=i;j++)
    			u[i][j] = max(u[i-1][j],u[i-1][j-1]) + num[i][j];
    	for (int i=n;i>=1;i--)
    		for (int j=1;j<=i;j++)
    			d[i][j] = max(d[i+1][j],d[i+1][j+1]) + num[i][j];
    	for (int i=1;i<=n;i++)
    	{
    		long long Max=0,sMax=0;
    		for (int j=1;j<=i;j++)
    		{
    			if ( val(i,j) > Max )
    			{
    				sMax = Max;Max = val(i,j);
    				pos[i] = make_pair( j , pos[i].first );
    			}
    			else if ( val(i,j) > sMax )
    			{
    				sMax = val(i,j);
    				pos[i].second = j;
    			}
    		}
    	}
    }
    inline void solve(void)
    {
    	for (int i=1;i<=m;i++)
    	{
    		int x,y;
    		scanf("%d%d",&x,&y);
    		if ( pos[x].first==y )
    			printf("%lld
    ",val(x,pos[x].second));
    		else printf("%lld
    ",val(x,pos[x].first));
    	}
    }
    int main(void)
    {
    	freopen("tower.in","r",stdin);
    	freopen("tower.out","w",stdout);
    	input();
    	dp();
    	solve(); 
    	return 0;
    }
    

    Market(DP换置)

    Description

    在比特镇一共有n 家商店,编号依次为1 到n。每家商店只会卖一种物品,其中第i 家商店的物品单价为ci,价值为vi,且该商店开张的时间为ti。
    Byteasar 计划进行m 次购物,其中第i 次购物的时间为Ti,预算为Mi。每次购物的时候,Byteasar会在每家商店购买最多一件物品,当然他也可以选择什么都不买。如果购物的时间早于商店开张的时间,那么显然他无法在这家商店进行购物。
    现在Byteasar 想知道,对于每个计划,他最多能购入总价值多少的物品。请写一个程序,帮助Byteasar 合理安排购物计划。

    注意:每次所花金额不得超过预算,预算也不一定要花完,同时预算不能留给其它计划使用

    Input Format

    第一行包含两个正整数n;m,表示商店的总数和计划购物的次数。
    接下来n 行,每行三个正整数ci; vi; ti,分别表示每家商店的单价、价值以及开张时间。
    接下来m 行,每行两个正整数Ti;Mi,分别表示每个购物计划的时间和预算。

    Output Format

    输出m 行,每行一个整数,对于每个计划输出最大可能的价值和。

    Sample Input

    5 2
    5 5 4
    1 3 1
    3 4 3
    6 2 2
    4 3 2
    3 8
    5 9
    

    Sample Output

    10
    12
    

    Limitation

    (nleq300,mleq10^5,c_i,M_ileq10^9,v_ileq300,T_i,t_ileq300)

    Solution

    这是经典(dp)模型(0/1)背包的(dp)换置。

    直观地考虑,这就是一道简单的(0/1)背包,但是这个背包的体积很大,不适合直接做。那么我们注意到数据中一个很好的限制:(v_ileq300),也就是说,我们可以考虑一种与价值有关的(dp)方式,这就用到(dp)换置了。

    (f_{i,j})代表前(i)家商店得到价值总和为(j)时的最小花费,这和普通背包问题的状态刚好相反,但是,其状态转移方程几乎是一样的:(f_{i,j}=min{f_{i-1,j-v_i}+c_i,f_{i-1,j}}),这样的(dp)的时间复杂度就只和(n,v)有关了。

    那我们如何得到答案呢?首先,我们需要对得到的(f)数组进行一些处理,显然有一些不能准确表示为若干个价值之和的位置的(f)值是正无穷,那么我们需要用得到更多价值的花费来填充该位置。完成这个操作后,我们就会发现(f)是具有单调性的,那么我们就可以二分了,二分找到第一个花费大于预算的下标,其上一个位置就是不超过预算的最大价值。

    在这道题当中,对于(t)有关时间的限制,我们将商店排成时间升序的,然后通过二分在找到时间上的最早开门时间,就可以通过第二次二分找到(f)数组中的答案了。

    (Code:)

    #include <bits/stdc++.h>
    using namespace std;
    const int N=301,M=100020,INF=0x3f3f3f3f3f;
    int n,m,t[N];
    long long f[N][N*N];
    struct market
    {
    	int c,v,t;
    }a[N];
    inline bool compare(market p1,market p2)
    {
    	return p1.t < p2.t;
    }
    inline void input(void)
    {
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++)
    	{
    		scanf("%d%d%d",&a[i].c,&a[i].v,&a[i].t);
    		t[i] = a[i].t;
    	}
    }
    inline void dp(void)
    {
    	sort(a+1,a+n+1,compare);sort(t+1,t+n+1);
    	for (register int i=1;i<=300*n;++i)f[0][i]=INF*1LL;
    	for (register int i=1;i<=n;++i)
    		for (register int j=0;j<=300*n;++j)
    			if (j>=a[i].v)
    				f[i][j] = min(f[i-1][j],f[i-1][j-a[i].v]+a[i].c);
    			else f[i][j] = f[i-1][j];
    	for (register int i=1;i<=n;++i)
    		for (register int j=300*n-1;j>=0;--j)
    			f[i][j] = min(f[i][j],f[i][j+1]);
    }
    inline void solve(void)
    {
    	for (register int i=1;i<=m;++i)
    	{
    		int T,M,ans=0;
    		scanf("%d%d",&T,&M);
    		int pos = upper_bound(t+1,t+n+1,T)-t-1;
    		ans = upper_bound(f[pos],f[pos]+300*n+1,M*1LL)-f[pos]-1;
    		printf("%d
    ",ans);
    	}
    }
    int main(void)
    {
    	input();
    	dp();
    	solve(); 
    	return 0;
    }
    

    Value(费用提前计算)

    Description

    给定(n)个物品,每个物品价值为(v_i)代价为(w_i)

    可以以任意顺序选择任意数量的物品,但在选择(i)号物品以后,剩下物品的价值就会减少(w_i),要求最大化选择商品的价值之和。

    Input Format

    第一行包括一个整数(n),剩下(n)行每行包括两个整数(v_i,w_i)

    Output Format

    一行包括共一个整数,代表价值之和的最大值。

    Sample Input

    5
    8 2
    10 7
    5 1
    11 8
    13 3
    

    Sample Output

    27
    

    Limitation

    (nleq5000,v_i,w_ileq10^5)

    Solution

    这道题的每一个物品选择都会对剩下的物品选择造成影响,如果直接(dp)的话将会出现后效性,导致(dp)错误,那么我们就需要对原来的数据做一些处理,然后利用费用提前计算的技巧,进行动态规划。

    首先,对于购买物品的最优组合(S={(v_{p_1},w_{p_1}),(v_{p_2},w_{p_2}),...,(v_{p_k},w_{p_k})}),显然按照(w)升序购买时收益最大。但是我们需要考虑每一个物品对之后物品的影响,所以我们要将所有物品按照(w)降序排序,然后设置倒序的状态:(f_{i,j})代表到物品(i)为止,已经选了后(j)个物品的最大价值和。这样,对于每一个物品,我们只考虑是否选它作为倒数第(j+1)个物品,那么就满足了贪心的原则,也方便了花费的计算。

    具体的,我们可以这样进行花费提前计算:(f_{i,j}=max(f_{i-1,j},f_{i-1,j-1}+v_{i}-(j-1)*w_i)),第一种情况代表第(i)个物品不选,第二种情况代表选它作为倒数第(j+1)个物品,在倒序状态中,我们实际上以及计算了它未来的影响:减少了最后(j-1)个物品(w_i)的价值。

    所以,对于有未来影响的(dp),我们可以使用花费提前计算的方法,当然,使用花费提前计算的方法通常还配合倒序的状态来使用。

    (Code:)

    #include <bits/stdc++.h>
    using namespace std;
    inline void read(int &k)
    {
    	int x=0,w=0;char ch;
    	while (!isdigit(ch)) 
    		w |= ch=='-' , ch = getchar();
    	while (isdigit(ch))
    		x = (x<<3) + (x<<1) + (ch^48) , ch=getchar();
    	k = ( w ? -x : x );
    } 
    const int N=5020;
    int n,ans,f[N][N];
    struct product
    {
    	int v,w;
    }a[N];
    inline bool compare(product p1,product p2)
    {
    	return p1.w > p2.w;
    }
    inline void input(void)
    {
    	read(n);
    	for (int i=1;i<=n;i++)
    		read(a[i].v) , read(a[i].w);
    }
    inline void dp(void)
    {
    	sort(a+1,a+n+1,compare);
    	for (int i=1;i<=n;i++)
    	{
    		for (int j=1;j<=n;j++)
    		{
    			f[i][j] = max(f[i-1][j],f[i-1][j-1]+a[i].v-(j-1)*a[i].w);
    			if (i==n) ans = max(ans,f[i][j]);
    		}
    	}
    }
    int main(void)
    {
    	freopen("value.in","r",stdin);
    	freopen("value.out","w",stdout); 
    	input();
    	dp();
    	printf("%d
    ",ans);
    	return 0;
    }
    

    <后记>

  • 相关阅读:
    通过pwndbg看看c中局部变量是如何在stack上放置的 此外 printf %n的作用终于弄明白了
    pip 安装过慢 使用清华源 加速
    mac 10.15.6 安装 IDA
    使用机器学习检测命令行混淆
    安全技能树简版
    栈溢出 hack 入门例子 hello world
    201116西瓜书机器学习系列---8、集成学习
    legend2---某些js代码电脑浏览器支持,手机浏览器不支持的调试
    legend2---做题页的每个题目对应的答案重点标颜色
    legend2---jquery重新渲染某元素
  • 原文地址:https://www.cnblogs.com/Parsnip/p/10786677.html
Copyright © 2020-2023  润新知