【问题描述】
小S 是一个很萌很萌的勤劳能干的好孩子,他准备在暑假里去打工。小S 能够打工的
天数总共为N,而在每一天可能领到不同的工资,其中第i 天打工能够领到的工资为A[i]
元。小S 的精力是有限的,所以他决定在每个连续的K 天里至少休息一天(即小S 最长连
续工作的天数不能大于等于K)。请你帮他安排在哪些天打工,使得他总共领到的工资最
多。
【输入】
第一行为两个正整数N、K。
第二行为N 个正整数A[i]。
【输出】
输出小S 能领到的最多的工资。
【输入输出样例】
work.in
5 3
1 2 3 1 4
work.out
9
【数据范围】
对于30%的数据:1 ≤ N ≤ 10。
对于60%的数据:1 ≤ N ≤ 2501。
对于100%的数据:1 ≤ K ≤ N ≤ 152501,1 ≤ A[i] ≤ 109。
题解:
【读题】
这道题根据题意可以抽象出这个模型:给你一个序列总个数为n,
你在其中每连续k个数都要选取一个数,问取出来的书的和最小是几
最后再用总和减去这个数
【样例解释】
5 3
1 2 3 1 4
在第2、3、5 天打工,第1、4 天休息。
【思路】
这道题根据数据范围、题设等因素,很容易看出来是一道dp
dp状态:dp[i]表示在a[i]必须取并满足条件时前i个数取得数的和最小是几
dp初值:dp[0]=0,dp[i]=INF{1<=i<=n}
dp转移:dp[i]=min{dp[j]}+a[i] (max(0,i-k)<=j<=i-1)
dp答案:ans=min(dp[i]){n-k+1<=i<=n}
最后输出sum(所有数的总和)-ans
时间复杂度O(n*k)
期望得分:60
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int NR=152600; int dp[NR]; int a[NR]; int ans=1e9; int sum=0; signed main() { int n,k; scanf("%lld%lld",&n,&k); memset(dp,127,sizeof(dp)); for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i]; dp[0]=0; for(int i=2;i<=n;i++) { for(int j=i-1;j>=max(i-k,0ll);j--) { dp[i]=min(dp[i],dp[j]+a[i]); } } for(int i=n;i>=n-k+1;i--) { ans=min(ans,dp[i]); } printf("%lld",sum-ans); return 0; }
【优化】
很明显,朴素的dp过不了这道题,所以需要用一下优化,
而这道题在每个dp[i]转移时,都是一些dp[j]+a[i]去取最小值
所以我们可以用一个线段树优化一下之前的代码,num[i]表示
线段树第i个节点对应的叶节点中最小的数是几,至于不会线段
树的同学们看一下这个网址:
https://www.cnblogs.com/chen-1/p/11440708.html
https://www.cnblogs.com/chen-1/p/11442635.html
第一个是单点修改讲解,第二个是区间修改讲解
时间复杂度O(n*logk)
期望得分:100
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int NR=152600; int dp[NR]; int a[NR]; int num[NR*4]; int sum=0; void build(int l,int r,int root) { if(l==r) { num[root]=dp[l]; return; } int mid=(l+r)/2; build(l,mid,root*2); build(mid+1,r,root*2+1); num[root]=min(num[root*2],num[root*2+1]); } void change(int x,int val,int l,int r,int root) { if(l==r) { num[root]=val; return; } int mid=(l+r)/2; if(x<=mid) change(x,val,l,mid,root*2); if(x>mid) change(x,val,mid+1,r,root*2+1); num[root]=min(num[root*2],num[root*2+1]); } int search(int p,int q,int l,int r,int root) { if(p==l && q==r) return num[root]; int mid=(l+r)/2; if(q<=mid) return search(p,q,l,mid,root*2); if(p>mid) return search(p,q,mid+1,r,root*2+1); return min(search(p,mid,l,mid,root*2),search(mid+1,q,mid+1,r,root*2+1)); } signed main() { int n,k; scanf("%lld%lld",&n,&k); for(int i=1;i<=n;i++) dp[i]=1e16; for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i]; dp[0]=0; dp[1]=a[1]; build(1,n,1); for(int i=2;i<=n;i++) { dp[i]=search(max(i-k,1ll),i-1,1,n,1)+a[i]; if(i-k<=0) dp[i]=min(dp[i],dp[0]+a[i]); change(i,dp[i],1,n,1); } printf("%lld",sum-search(n-k+1,n,1,n,1)); return 0; } /* 15 5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */