Day 6 动态规划

大部分问题都是背包问题的变式,建议b站搜索背包九讲加以理解。

#美丽度
Description
众所周知,strawberry的妹子很多而且总数甚至是不可数的,妹子集合和阿列夫零等势。
今天strawberry把他的 n 个妹子带来,排成一排。strawberry的妹子很多,但是质量不容乐观。每个妹子有美丽度,有正有负。strawberry想选出其中3队妹子,使得她们的美丽度之和最大。每一队都应该是一个连续的区间,而且两队不能同时选择同一个妹子。
strawberry又去找新的妹子了,请你帮帮strawberry算算最大美丽度是多少?
Input
输入一行一个数 n,表示strawberry这次带来的妹子数。接下来一行 n 个数,分别表示每个妹子的美丽度 a 下標 i。
Output
输出一个数,表示最大美丽度。
Note
所有数据保证 3 小於等於 n 小於等於 500000 逗號 負 10 的 9 次方 小於等於 a 下標 i 小於等於 10 的 9 次方。
样例解释:取的三个不相交连续子串分别是{2,3} {1} {1}。
代码:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
long long int FMAX(long long int a,long long int b){
	if(a>b)
		return a;
	else
	 	return b;
}
const long long int lenth=500000;
long long int n,i,j;
long long int dp[4][lenth],c[lenth];
int main(){
	scanf("%lld\n",&n); //scanf别忘记地址符号  
	for(i=1;i<=n;i++)
		scanf("%lld ",&c[i]);
		
	for(i=0;i<n;i++){
		dp[0][i]=0;
		dp[1][i]=0;
	}
	
	for(i=1;i<=3;i++){
		long long int before=-1000000001;
		for(j=i;j<=n;j++){
			if(i==j){
				dp[i][j]=dp[i-1][j-1]+c[j];
				continue;	
			}	
			before=FMAX(before,dp[i-1][j-1]);
			dp[i][j]=FMAX(dp[i][j-1],before)+c[j];
		}
	}
		long long int max1=dp[3][3];
		for(i=4;i<=n;i++){
			if(dp[3][i]>max1)
				max1=dp[3][i];
		}
		printf("%lld\n",max1);
		return 0;
	}

#一道背包问题
Description
龙神有很多背包,每一个背包都有一个容积。但是这些背包的容积都恰好是一个数字V的整数倍,比如V,2 V,等等。并且对于任意k 大於等於 1,容积为k 乘號 V的背包都存在。

龙神有一些物品要装进背包,第i个物品占据p 下標 i的体积。现在,龙神想选出一些物品,使得存在一个背包可以恰好放下这些物品,并且这个背包放满。

龙神想知道这样的取法有多少个,请你帮他算一算吧?由于取法很多,你只需要输出取法的末七位数,没有前导零,即可(即对10000000取模)。

Input
第一行两个数n 逗號 V,分别表示物品数和背包体积的基数。

第二行n个数,分别表示每个物品的体积p 下標 i。

Output
输出一行一个数,表示取法总数的末七位。

Note
数据保证1 小於等於 n 逗號 V 小於等於 2000 逗號 1 小於等於 p 下標 i 小於等於 100000。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
long long int dp[2001][2001];
long long int a[2001];
long long int temp,j,i,n,v;
int main(){
	memset(dp,0,sizeof(dp));
	scanf("%lld %lld\n",&n,&v);	
	dp[0][0]=1;
	
	for(i=1;i<=n;i++){
		scanf("%lld ",&a[i]);
		for(j=0;j<v;j++){
			dp[i][j]=(dp[i-1][j]+dp[i][j])%10000000;
		}
		for(j=0;j<v;j++){
			temp=(j+a[i])%v;
			dp[i][temp]=(dp[i-1][j]+dp[i][temp])%10000000;
		}
	}
	printf("%lld\n",dp[n][0]-1);
	return 0;
}

#方向标
Description
A carpenter has received an order for a wooden directional sign. Each board must be aligned vertically with the previous one, either to the basis of the previous arrowhead or to the opposite side, being fixed there with a specially designed screw. The two boards must overlap. The carpenter wrote down a sequence of integers to encode the sketch sent by the designer but the sequence does not determine a unique model and he has thrown away the original sketch. What looked like a trivial task turned out a big jigsaw to him.

The sequence (with 1 + N elements) encodes the (N) arrows from the bottom to the top of the sign. The first element is the position of the left side of the bottom arrow. The remaining N elements define the positions where the arrowheads start, from bottom to top: the i-th element is the position where the i-th arrowhead basis is. For instance, the two signs depicted (on the left and on the right) could be encoded by 2 6 5 1 4.

Since a board must be aligned vertically with the previous one (either to the basis of the previous arrowhead or to the opposite side), if the sequence was 2 6 5 1 4 3, the fifth arrow could be fixed (in any of the depicted signs) with a screw either at 1 (pointing to the right) or at 4 (pointing to the left), with the arrowhead basis at 3.

If the sequence was 2 3 1, the second arrow could only be fixed with a screw at 3, pointing to the left, because consecutive boards must overlap.

All arrowheads are similar and the designer told the carpenter that their bases stand in different vertical lines, as well as the left side of the bottom arrow, altogether forming a permutation of 1…(N +1). That is why the carpenter overlooked the details and just wrote down the permutation (e.g., 2 6 5 1 4 3).

Given the sequence of numbers the carpenter wrote down, compute the number of directional arrows signs that can be crafted. Since the number can be very large, you must write it modulo 2147483647. The second integer in the sequence is always greater than the first one (the bottom arrow points to the right always).

Input
The first line has one integer N and the second line contains a permutation of the integers from 1 to N + 1. Integers in the same line are separated by a single space.

Output
The output has a single line with the number (modulo 2147483647) of distinct signs that can be described by the given permutation.

Note
1 小於等於 N 小於等於 2000

#include <bits/stdc++.h> 
    using namespace std; 
    typedef long long ll; 
    typedef unsigned long long ull; 
    typedef pair<int, int> pii; 
    const int INF = 0x3f3f3f; 
    const double EPS = 1e-8; 
    const double PI = acos(-1); 
    #define __MAX 200010 
    #define __BASE 2147483647 
     
    vector<int> link[__MAX]; //邻接表
    vector<pii> side; //用于保存输入的边
    int child[__MAX]; //用于保存子树的大小(即子节点数量+1)
    int dep[__MAX]; 
    int n;
    /*用于保存节点的深度,事实上,这里不需要这样做,只需要在过程中传递“是否为奇数节点”这个变量,
    并且累加到count_odd上就行,感兴趣的同学可以试试curcle(int cur, bool is_odd)这样的写法*/
    int count_odd = 0; //奇数节点的数量
     
    inline int read() //快读,为了防止超时而做的一点优化,有大量数据输入的时候比scanf和cin快很多,事后发现应该没有这个必要
    { 
        int x=0,f=1;
		char ch=getchar(); 
        while(ch<'0'||ch>'9') 
        { 
            if(ch=='-') 
                f=-1; 
            ch=getchar(); 
        } 
        while(ch>='0'&&ch<='9') 
        { 
            x=x*10+(ch-'0'); 
            ch=getchar(); 
        } 
        return f*x; 
    } 
    void curcle(int cur, int depth){ 
        int total_child = 0; 
        dep[cur] = depth; 
        if(depth & 1) count_odd += 1; //判断是否为奇数,用位运算更快
        for(int i: link[cur]){ 
            if(dep[i] != -1) continue; //深度不为-1,说明已遍历过,跳过
            else{ //未遍历过,将其纳入cur节点的子节点中
                curcle(i, depth + 1); 
                total_child += child[i]; //递归地计算总节点数
            } 
        } 
        child[cur] = total_child + 1; //加上节点自己
    } 
    void total(){
    	 ll set = 0; //记得开long long
        for(pii line: side){ //遍历所有输入的边,如果没有保存输入的话,这里再用一次curcle遍历也是可以的
            //ll anc = max(child[line.first], child[line.second]); 
            ll son = min(child[line.first], child[line.second]); 
            set += son * (n - son); 
        } 
        set += (ll)count_odd * (ll)(n - count_odd); //涉及到整型变量相乘溢出,记得先做类型转换
     	printf("%lld\n",set/2);
	}
    int main(){ 
        
        void curcle(int cur, int depth); 
        scanf("%d\n",&n);
        memset(dep, -1, sizeof(dep)); 
        for(int i = 0; i < n - 1; i++){ 
            int u, v; 
            u = read(); 
            v = read(); 
            link[u].push_back(v); //更新邻接表
            link[v].push_back(u); 
            side.emplace_back(u,v); //保存输入边
        } 
        curcle(1,1); //假定第一个节点为根节点,深度为1,开始curcle,事实上,这里的根节点可以任意设置,初始深度也可以任意设置,可以自己想想为什么
     	total();
        return 0; 
    } 
     
     

#树上统计
Background
除了上课答疑之外,给同学们出题也是小学期助教的工作之一,今天又轮到R o a r k出题了。

R o a r k本打算出这样一道题:

“给出一个有n个节点,n 減 1条边的连通图,每条边长均为1。求对于所有的点对左小括號 u 逗號 v 右小括號,加總 從 空白 到 空白 對 d i s t 左小括號 u 逗號 v 右小括號。其中d i s t 左小括號 u 逗號 v 右小括號指的是左小括號 u 逗號 v 右小括號之间的最短距离。”

但当他把题目说给D a r k D a w n时,却得到了这样的答复:

R o a r k自然不敢违抗D a r k D a w n的命令T^T,只好稍稍增加了一点难度,题目就变成了现在的样子。

Description
给出一个有n个节点,n 減 1条边的连通图,每条边长均为1。除此之外,现在原图上的每两个不相邻且仅间隔一个节点的节点间建边,边长仍为1。

求对于所有的点对左小括號 u 逗號 v 右小括號,加總 從 空白 到 空白 對 d i s t 左小括號 u 逗號 v 右小括號。其中d i s t 左小括號 u 逗號 v 右小括號指的是左小括號 u 逗號 v 右小括號之间的最短距离。

Input
第一行输入n,表示节点数左小括號 2 小於等於 n 小於等於 200000 右小括號。

接下来n 減 1行每行两个整数a 逗號 b,表示原图在节点a和节点b之间有一条边。

Output
输出加總 從 空白 到 空白 對 d i s t 左小括號 u 逗號 v 右小括號
答案:点击参考

 #include <bits/stdc++.h> 
    using namespace std; 
    typedef long long ll; 
    typedef unsigned long long ull; 
    typedef pair<int, int> pii; 
    const int INF = 0x3f3f3f; 
    const double EPS = 1e-8; 
    const double PI = acos(-1); 
    #define __MAX 200010 
    #define __BASE 2147483647 
     
    vector<int> link[__MAX]; //邻接表
    vector<pii> side; //用于保存输入的边
    int child[__MAX]; //用于保存子树的大小(即子节点数量+1)
    int dep[__MAX]; 
    /*用于保存节点的深度,事实上,这里不需要这样做,只需要在过程中传递“是否为奇数节点”这个变量,
    并且累加到count_odd上就行,感兴趣的同学可以试试dfs(int cur, bool is_odd)这样的写法*/
    int count_odd = 0; //奇数节点的数量
     
    inline int read() //快读,为了防止超时而做的一点优化,有大量数据输入的时候比scanf和cin快很多,事后发现应该没有这个必要
    { 
        int x=0,f=1;char ch=getchar(); 
        while(ch<'0'||ch>'9') 
        { 
            if(ch=='-') 
                f=-1; 
            ch=getchar(); 
        } 
        while(ch>='0'&&ch<='9') 
        { 
            x=x*10+(ch-'0'); 
            ch=getchar(); 
        } 
        return f*x; 
    } 
     
    int main(){ 
        ///ifstream infile("input.txt", ios::in); 
        ///ofstream outfile("output.txt", ios::out); 
     
        int n; 
     
        void dfs(int cur, int depth); 
        cin >> n; 
        memset(dep, -1, sizeof(dep)); 
        for(int i = 0; i < n - 1; i++){ 
            int u, v; 
            u = read(); 
            v = read(); 
            link[u].push_back(v); //更新邻接表
            link[v].push_back(u); 
            side.emplace_back(u,v); //保存输入边
        } 
        dfs(1,1); //假定第一个节点为根节点,深度为1,开始DFS,事实上,这里的根节点可以任意设置,初始深度也可以任意设置,可以自己想想为什么
     
        ll res = 0; //记得开long long
        for(pii line: side){ //遍历所有输入的边,如果没有保存输入的话,这里再用一次DFS遍历也是可以的
            //ll anc = max(child[line.first], child[line.second]); 
            ll son = min(child[line.first], child[line.second]); 
            res += son * (n - son); 
        } 
        res += (ll)count_odd * (ll)(n - count_odd); //涉及到整型变量相乘溢出,记得先做类型转换
     
        cout << res / 2 << endl; 
     
        return 0; 
    } 
     
     
     
    void dfs(int cur, int depth){ 
        int total_child = 0; 
        dep[cur] = depth; 
        if(depth & 1) count_odd += 1; //判断是否为奇数,用位运算更快
        for(int i: link[cur]){ 
            if(dep[i] != -1) continue; //深度不为-1,说明已遍历过,跳过
            else{ //未遍历过,将其纳入cur节点的子节点中
                dfs(i, depth + 1); 
                total_child += child[i]; //递归地计算总节点数
            } 
        } 
        child[cur] = total_child + 1; //加上节点自己
    } 

#买瓜人
军训这么累,当然要吃瓜啦。

有一个小军前来买瓜。

众所周知,YHW水果摊里的西瓜的瓜皮子和瓜粒子都是金粒子做的。小军当然想获得尽可能多的金粒子。

一共有n个瓜,每个瓜有重量w,成熟度v,金粒子数量g。

虽然小军的钱是无限的,但他的电动车载重是有限的,因此他不能买总重量超过W的瓜。

"我开水果摊的,能卖给你生瓜蛋子? ——YHW

因此,当小军买的瓜的总成熟度小于V时,YHW并不会把瓜卖给他。

具体的:水果摊一共有n个瓜,每个瓜有重量w,成熟度v,金粒子数量g。一共有q次询问,每次询问给出两个值W、V,小军要选取n个瓜的子集,使总重量 Σw≤W,总成熟度Σv≥V,求 ΣgΣg的最大值。

Input
第一行输入两个整数n,q(1≤n≤100,1≤q≤100)n,q(1≤n≤100,1≤q≤100),代表瓜的数量和询问数量。

接下来n行,每行三个整数,第i行的三个整数为wi,vi,gi(1≤wi,vi,gi≤100)wi,vi,gi(1≤wi,vi,gi≤100),分别代表第i个瓜的重量,成熟度和价值。

接下来q行,输入两个整数W,V(1≤W,V≤500)W,V(1≤W,V≤500),代表一组询问。

Output
输出一共有q行,每行一个整数,分别代表每组询问的最大金粒子数总和,若无合法的买法则输出-1。

代码:

#include <bits/stdc++.h> 
    using namespace std; 
    typedef long long ll; 
    typedef unsigned long long ull; 
    typedef pair<int, int> pii; 
    const int INF = 0x3f3f3f; 
    const double EPS = 1e-8; 
    const double PI = acos(-1); 
    #define __MAX 200010 
    #define __BASE 2147483647 
     
    vector<int> link[__MAX]; //邻接表
    vector<pii> side; //用于保存输入的边
    int child[__MAX]; //用于保存子树的大小(即子节点数量+1)
    int dep[__MAX]; 
    int n;
    /*用于保存节点的深度,事实上,这里不需要这样做,只需要在过程中传递“是否为奇数节点”这个变量,
    并且累加到count_odd上就行,感兴趣的同学可以试试curcle(int cur, bool is_odd)这样的写法*/
    int count_odd = 0; //奇数节点的数量
     
    inline int read() //快读,为了防止超时而做的一点优化,有大量数据输入的时候比scanf和cin快很多,事后发现应该没有这个必要
    { 
        int x=0,f=1;
		char ch=getchar(); 
        while(ch<'0'||ch>'9') 
        { 
            if(ch=='-') 
                f=-1; 
            ch=getchar(); 
        } 
        while(ch>='0'&&ch<='9') 
        { 
            x=x*10+(ch-'0'); 
            ch=getchar(); 
        } 
        return f*x; 
    } 
    void curcle(int cur, int depth){ 
        int total_child = 0; 
        dep[cur] = depth; 
        if(depth & 1) count_odd += 1; //判断是否为奇数,用位运算更快
        for(int i: link[cur]){ 
            if(dep[i] != -1) continue; //深度不为-1,说明已遍历过,跳过
            else{ //未遍历过,将其纳入cur节点的子节点中
                curcle(i, depth + 1); 
                total_child += child[i]; //递归地计算总节点数
            } 
        } 
        child[cur] = total_child + 1; //加上节点自己
    } 
    void total(){
    	 ll set = 0; //记得开long long
        for(pii line: side){ //遍历所有输入的边,如果没有保存输入的话,这里再用一次curcle遍历也是可以的
            //ll anc = max(child[line.first], child[line.second]); 
            ll son = min(child[line.first], child[line.second]); 
            set += son * (n - son); 
        } 
        set += (ll)count_odd * (ll)(n - count_odd); //涉及到整型变量相乘溢出,记得先做类型转换
     	printf("%lld\n",set/2);
	}
    int main(){ 
        
        void curcle(int cur, int depth); 
        scanf("%d\n",&n);
        memset(dep, -1, sizeof(dep)); 
        for(int i = 0; i < n - 1; i++){ 
            int u, v; 
            u = read(); 
            v = read(); 
            link[u].push_back(v); //更新邻接表
            link[v].push_back(u); 
            side.emplace_back(u,v); //保存输入边
        } 
        curcle(1,1); //假定第一个节点为根节点,深度为1,开始curcle,事实上,这里的根节点可以任意设置,初始深度也可以任意设置,可以自己想想为什么
     	total();
        return 0; 
    } 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值