题目大意
有一堆项目,每个项目有三个权值 ( c o s t 0 , c o s t 1 , c o l o r ) (cost_0,cost_1,color) (cost0,cost1,color),你要选择一些项目顺序执行,并给出你总花费的计算方式,求花费第k 大的工作方式。
题目分析
建图
转化为求k短路的话,必须要把权值取相反数,然后答案取相反数。
把每个项目
i
i
i拆成三个点
i
1
i_1
i1,
i
2
i_2
i2,
i
3
i_3
i3,连边
(
i
1
,
i
3
,
−
c
o
s
t
0
)
(
i
2
,
i
3
,
−
c
o
s
t
1
)
(i_1,i_3,-cost_0)(i_2,i_3,-cost_1)
(i1,i3,−cost0)(i2,i3,−cost1)表示两种花钱方式,根据i的color连边
(
i
3
,
(
i
+
1
)
1
,
0
)
(i_3,(i+1)_1,0)
(i3,(i+1)1,0)或
(
i
3
,
(
i
+
1
)
1
,
0
)
(i_3,(i+1)_1,0)
(i3,(i+1)1,0),连边
(
i
1
,
(
i
+
1
)
1
,
0
)
(
i
2
,
(
i
+
1
)
2
,
0
)
(i_1,(i+1)_1,0)(i_2,(i+1)_2,0)
(i1,(i+1)1,0)(i2,(i+1)2,0)表示不做这个项目.
起点为
1
1
1_1
11,新建终点,
n
1
n_1
n1,
n
2
n_2
n2,
n
3
n_3
n3连到终点上。
可持久化左偏树
首先由于图的性质,可以通过拓扑计算从终点到其他点在反向图上的最短路,即为该点在正向图上走最短路到终点的距离 d i s dis dis.这一步上,我们也建立出了一棵终点的最短路树,分出了非树边与树边。
然后我们希冀每走一步就能获得一条新的当前最短路径的算法。
我们知道,走一条路,那便是走若干树边和若干非树边.所以我们的更新可以是每次在当前路径的基础上多走一条非树边。
设非树边 ( u , v ) (u,v) (u,v)的权值为 d i s v + w ( u , v ) − d i s u dis_v+w(u,v)-dis_u disv+w(u,v)−disu,即减去原来这一段都走树边的贡献,加上走一条非树边的贡献。
利用可持久化左偏树(每次合并的时候新建节点即可),对于每一个点维护一棵小根堆左偏树,里面储存从该点走树边走到终点的路径上,与这条路径相连的非树边有哪些。
首先,第1短路是从S开始不走非树边的路。开一个优先队列,来维护现在的“第 t t t短路候选路径”,求第 t t t短路时,就从这个候选队列里取出最短的一条路径作为第 t t t短路径。在确定了第 t t t短路径后,有几条比该路径稍长的路径也该被加入候选队列中,它们是:
- 在走了第 t t t短路径上的最后一条非树边后,假设我站在 v v v这个点上,那么我在我剩下的前进道路中,再选一条非树边走一下。
- 不走第 t t t短路径的最后一条非树边,而是走它在堆中的左儿子/右儿子。
于是这个候选队列中存放的候选路径的信息就只有,路径长度和走的最后一条非树边了。
可能有点没讲清楚,具体可以看一下work函数。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
#define LL long long
#define pr pair<LL,int>
const int N=150005;
const LL inf=1e15;
int T,n,K,s,t,t0,t1,tot,top;
int h0[N],h1[N],pre[N],rt[N],st[N],du[N];LL dis[N];
struct edge{int w,to,ne;}e0[N<<1],e1[N<<1];
void add(int x,int y,int z) {
e0[++t0].to=y,e0[t0].ne=h0[x],h0[x]=t0,e0[t0].w=z;
e1[++t1].to=x,e1[t1].ne=h1[y],h1[y]=t1,e1[t1].w=z;
++du[x];
}
void bg() {//build_graph
s=n+1,t=n*3+1;int c0,c1,co;
t0=t1=0;for(int i=1;i<=t;++i) h0[i]=h1[i]=du[i]=0,dis[i]=inf;
for(int i=1;i<=n;++i) {
c0=read(),c1=read(),co=read();
add(n+i,i,-c0),add(n+n+i,i,-c1);
if(i!=n) {
add(n+i,n+i+1,0),add(n+n+i,n+n+i+1,0);
if(co) add(i,n+n+i+1,0);
else add(i,n+i+1,0);
}
else add(n,t,0),add(n+n,t,0),add(n+n+n,t,0);
}
}
void topo() {
top=1,st[top]=t,dis[t]=pre[t]=0;
while(top) {
int x=st[top--];
for(int i=h1[x];i;i=e1[i].ne) {
if(dis[e1[i].to]>dis[x]+e1[i].w)
dis[e1[i].to]=dis[x]+e1[i].w,pre[e1[i].to]=x;
--du[e1[i].to];if(!du[e1[i].to]) st[++top]=e1[i].to;
}
}
}
struct node{int ls,rs,d,id;LL v;}a[N*40];
int merge(int x,int y) {
if(x*y==0) return x+y;
if(a[x].v>a[y].v) swap(x,y);
int o=++tot;a[tot]=a[x];
a[o].rs=merge(a[o].rs,y);
if(a[a[o].ls].d<a[a[o].rs].d) swap(a[o].ls,a[o].rs);
a[o].d=a[a[o].rs].d+1;
return o;
}
void dfs(int x) {//每个左偏树代表从i到t的路上的所有非树边
if(pre[x]) rt[x]=rt[pre[x]];
for(int i=h0[x];i;i=e0[i].ne) {
int v=e0[i].to;
if(dis[v]==inf||pre[x]==v) continue;
a[++tot]=(node){0,0,1,v,dis[v]+e0[i].w-dis[x]},rt[x]=merge(rt[x],tot);
}
for(int i=h1[x];i;i=e1[i].ne)
if(pre[e1[i].to]==x) dfs(e1[i].to);
}
void work() {
tot=0,rt[t]=0,dfs(t);
if(K==1) {printf("%lld\n",-dis[s]);return;}
priority_queue<pr,vector<pr >,greater<pr > > q;
q.push(pr(dis[s]+a[rt[s]].v,rt[s]));
while(K--) {//利用优先队列获得答案
pr kl=q.top(); q.pop();
if(K==1) {printf("%lld\n",-kl.first);return;}
int u=kl.second,v=a[u].id;
if(rt[v]) q.push(pr(kl.first+a[rt[v]].v,rt[v]));
if(a[u].ls) q.push(pr(kl.first-a[u].v+a[a[u].ls].v,a[u].ls));
if(a[u].rs) q.push(pr(kl.first-a[u].v+a[a[u].rs].v,a[u].rs));
}
}
int main() {
T=read();
while(T--) {
n=read(),K=read();
bg(),topo(),work();
}
return 0;
}