题意:给定一个长为n数组a和整数c。把a分成若干连续段,每一段对应的值等于其对应sum减去段长k除以c向下取整各的最小几个数,求所有段值的和。
思路:贪心,必定是把a分成若干长为c和1的段。线段树查询区间最小值。
线段树+dp代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdio.h>
#define L(x) (x << 1)//左儿子
#define R(x) (x << 1 | 1)//右儿子
#define sz(x) (tree[x].r - tree[x].l + 1)//区间大小
using namespace std;
typedef long long ll;
const int MAXN = 100005;
const int inf=0x3f3f3f3f;
ll n,m,a,b,c,num[MAXN];
ll sum[MAXN];
ll dp[MAXN];
// l == 左;r == 右;p == now;
struct tree{
int l,r;//修改求和
int min;//最值标记
}tree[MAXN << 2];
//标记下放们 p == now
void update(int p){
tree[p].min = min(tree[L(p)].min,tree[R(p)].min);
}
//简单的建立
void build(int l,int r,int p){
tree[p].l = l,tree[p].r = r;
if(l == r){
tree[p].min=num[l];
return;
}
int mid = (tree[p].l + tree[p].r) >> 1;
build(l,mid,L(p)); build(mid + 1,r,R(p));
update(p);
}
//区间最小值
int ask_min(int l,int r,int p){
if(l <= tree[p].l && tree[p].r <= r)
return tree[p].min;
int ans = inf,mid = (tree[p].l + tree[p].r) >> 1;
if(l <= mid) ans = min(ans,ask_min(l,r,L(p)));
if(mid < r) ans = min(ans,ask_min(l,r,R(p)));
update(p); return ans;
}
int main(){
memset(dp,0,sizeof(dp));
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
scanf("%d",&n);
scanf("%d",&c);
for(int i = 1; i <= n; i ++)
{
scanf("%d",&num[i]);
dp[i]=sum[i]=sum[i-1]+num[i];
}
build(1,n,1);
for(int i=c;i<=n;i++)
{
dp[i]=min(dp[i-1]+num[i],dp[i-c]+sum[i]-sum[i-c]-ask_min(i-c+1,i,1));
}
printf("%I64d\n",dp[n]);
return 0;
}
multiset + dp代码:
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;
const int maxn = 100007;
int a[maxn],n,c;
ll dp[maxn],sum[maxn];
multiset<int> st;
int main(){
scanf("%d%d",&n,&c);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i]);
dp[i] = sum[i] = sum[i-1] + a[i];
}
for(int i = 1;i < c;++i) //把前c个元素加入multiset
st.insert(a[i]);
for(int i = c;i <= n;++i){
st.insert(a[i]); //加入新元素
dp[i] = min(dp[i-1] + a[i],dp[i-c] + sum[i] - sum[i-c] - *st.begin()); //两种分法比较
st.erase(st.find(a[i-c+1])); //去除旧元素
}
printf("%lld\n",dp[n]);
return 0;
}