题解 P2034 【选择数字】
PurpleWonder · · 题解
蒟蒻并没有想到dp做法……于是用最短路做的这道题,跑的dp慢许多。
因为本题需要在每隔最多k个数字就要放弃一个,所以对于每一个数字i,向它后面的i+1~i+k+1连边,之后设一个超级起点s连向1~k,再将n-k+1~n的节点连向一个超级终点t,之后以s为起点跑最短路,所有阶段权值的总和sum减去s到t的距离就是最后的答案。
由于需要连得边数是O(nk)级别的,并且是一个点向一个区间的点连一条边,因此可以用线段树优化建图,讲连的边数变成O(nlogk),总的时间复杂度是O(nlognlogn),正好可以卡过1e5的数据
代码:
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#define mk(x,y) (node){x,y}
using namespace std;
long long sum,ju[400010];
int n,k;
int a[100010],wz[100010];
int nxt[2000010],to[2000010],head[2000100],quan[2000100],gs;
int d[400010],l[400010],r[400010];
vector<int> v;
struct node{int v;long long jv;bool operator <(const node d)const{return jv>d.jv;}};
priority_queue<node> q;
inline void lb(int x,int y,int z){
nxt[++gs]=head[x];
head[x]=gs;
to[gs]=y;
quan[gs]=z;
}
void build(int x,int li,int ri){
l[x]=li;r[x]=ri;
if(li==ri){
wz[li]=x;
return;
}
int mid=(li+ri)>>1;
build(x<<1,li,mid);
build(x<<1|1,mid+1,ri);
lb(x,x<<1,li==mid?a[li]:0);
lb(x,x<<1|1,mid+1==ri?a[ri]:0);
}
void query(int x,int li,int ri){
if(l[x]>ri || r[x]<li)return;
if(l[x]>=li && r[x]<=ri){v.push_back(x);return;}
query(x<<1,li,ri);query(x<<1|1,li,ri);
}
void dij(){
memset(ju,0x3f,sizeof(ju));
ju[0]=0;
q.push(mk(0,0));
while(!q.empty()){
node dq=q.top();q.pop();
int x=dq.v;
if(ju[x]!=dq.jv)continue;
for(int i=head[x];i;i=nxt[i]){
if(ju[to[i]]>ju[x]+quan[i]){
ju[to[i]]=ju[x]+quan[i];
q.push(mk(to[i],ju[to[i]]));
}
}
}
}
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i];
build(1,1,n+1);
for(int i=0;i<=n;i++){
query(1,i+1,min(i+k+1,n+1));
for(vector<int>::iterator id=v.begin();id!=v.end();id++){
lb(wz[i],*id,l[*id]==r[*id]?a[l[*id]]:0);
}
v.clear();
}
dij();
printf("%lld",sum-ju[wz[n+1]]);
}