你在玩一款益智推箱子游戏。
数轴上有 n n n个箱子和 m m m个传送点,第 i i i个箱子在 x i x_i xi,第 i i i个传送点在 y i y_i yi。当箱子放在传送点上时可以传送走,每个传送点可以最多送走一个箱子,传送走所有箱子视为通关。
你可以做的唯一操作是将一个箱子向左推一个单位,耗费代价1,过程中允许有多个箱子在同一位置或者箱子在传送点位置但没被送走,问你通关的最小代价。
只要从左到右依次考虑每个物件(传送点或箱子),然后将箱子送入最右边还未被使用的传送点即可,用一个栈就能完成。
你在开始推箱子前,需要选择若干个传送点激活,只有激活了的传送点才能使用,激活第 i i i个传送点的代价是 w i w_i wi。箱子只能往左推,推一个单位花费1的代价。
不一定要传送走所有箱子,最大化代价。
依然从左往右考虑每个物件。
若物件是一个箱子 a a a,推入传送点 b b b产生代价是 x a − y b + w b x_a-y_b+w_b xa−yb+wb,所以要取 − y b + w b -y_b+w_b −yb+wb最大的传送点,用堆维护传送点即可。若 x a − y b + w b x_a-y_b+w_b xa−yb+wb一定小于0,则不使用该箱子。
但是这只是个只顾眼前利益的贪心而已,可能最优的解是 a a a之后的一个箱子 c c c和传送点 b b b匹配, a a a不匹配呢?如果要更改 b b b匹配的箱子的话,应该要在答案里减去箱子 a a a造成的贡献,即相当于添加一个价值为 − x a -x_a −xa的箱子。
取消 a a a与 b b b的匹配,改为 c c c与 b b b的匹配,这很像一个费用流的退流操作(事实上后面的模型会更像,因为这个还只是退流一次,没有什么 c c c抢走 b b b, a a a就去匹配它本来匹配过的 d d d,然后现在匹配 d d d的节点又去……这种),实际上模拟费用流的本质就是用其他东西来模拟退流。
bzoj4977 跳伞求生
#include
using namespace std;
#define RI register int
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;
}
typedef long long LL;
int n,m;LL ans;
struct node{int x,c;}d[200005];
bool cmp(node A,node B) {return A.x==B.x?A.c==-1:A.x<B.x;}
priority_queue<int> q;
int main()
{
n=read(),m=read();
for(RI i=1;i<=n;++i) d[i].x=read(),d[i].c=-1;
for(RI i=1;i<=m;++i) d[n+i].x=read(),d[n+i].c=read();
sort(d+1,d+1+n+m,cmp);
for(RI i=1;i<=n+m;++i) {
if(d[i].c==-1) {
if(q.empty()||q.top()+d[i].x<=0) continue;
ans+=q.top()+d[i].x,q.pop(),q.push(-d[i].x);
}
else q.push(d[i].c-d[i].x);
}
printf("%lld\n",ans);
return 0;
}
在任何一种条件组合的基础上,箱子必须被传送掉,最小化代价。
那么先让每个箱子都与一个激活代价为 I N F INF INF的传送点匹配,这个匹配是不可退流的。
箱子必须匹配一个传送点,传送点最多匹配一个箱子,传送点有激活代价,箱子可以向左推也可以向右推。
同样从左往右考虑每个物件。
若是一个箱子 a a a,在传送点堆中找一个代价最小的传送点 b b b,假设代价为 v b v_b vb,那么现在产生的贡献是 x a + v b x_a+v_b xa+vb。
考虑后续有一个箱子要抢夺传送点 b b b,理论上应该加一个代价为 − x a -x_a −xa的传送点,不过因为箱子都必须匹配,所以完成 a a a与 b b b的匹配时其实是除去了一个 I N F INF INF的 a a a本来和代价 I N F INF INF的虚拟传送点匹配的代价,所以应该加的是代价为 I N F − x a INF-x_a INF−xa的传送点,也就不用加这个传送点了。
考虑后续有一个传送点要抢夺箱子 a a a,除去贡献,添加一个价值为 − v b − 2 x a -v_b-2x_a −vb−2xa的箱子。
若是一个传送点 b b b,在箱子堆中找一个代价最小的箱子 b b b,设代价为 v b v_b vb,那么现在产生的贡献是 y a + w a + v b y_a+w_a+v_b ya+wa+vb。
考虑后续有一个箱子要抢夺传送点 b b b,添加一个代价为 − y a − w a -y_a-w_a −ya−wa的传送点。
考虑后续有一个传送点要抢夺箱子 a a a,添加一个代价为 − v b − 2 y a -v_b-2y_a −vb−2ya的箱子。
在任意条件组合基础上,添加“传送点最多可以激活 c i c_i ci次,每次激活代价为 w i w_i wi”或“第 i i i个位置叠着 d i d_i di个箱子”这两个条件, c i c_i ci和 d i d_i di都较大( 1 0 9 10^9 109级别)
在堆里直接放一个pair,记录一下这种箱子或传送点有多少个即可。什么?你说合并的时候产生的新箱子和新传送点数目可能会很多?呃,这个是可以证明不会产生太多的,好像是用什么匹配不会交叉证,但我不会,嘤嘤嘤。
接下来可以做一道题了,UOJ#455 雪灾与外卖
#include
using namespace std;
#define RI register int
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;
}
typedef long long LL;
typedef pair<LL,LL> PR;
const LL inf=1e15;
const int N=100005;
int n,m;LL ans,sumc;
struct node{LL x,w,c;}t[N<<1];
bool cmp(node A,node B) {return A.x<B.x;}
priority_queue<PR,vector<PR>,greater<PR> > q1,q2;
void work() {
q1.push((PR){inf,n});
for(RI i=1;i<=n+m;++i) {
if(t[i].w==-1) {
PR kl=q1.top();LL kv=kl.first;q1.pop();
ans+=kv+t[i].x,--kl.second;
if(kl.second) q1.push(kl);
q2.push((PR){-kv-2*t[i].x,1});
}
else {
LL num=t[i].c,sumsum=0;
while(num&&(!q2.empty())) {
PR kl=q2.top();LL kv=kl.first;
if(kv+t[i].w+t[i].x>=0) break;
q2.pop();LL sum=min(kl.second,num);
ans+=(kv+t[i].w+t[i].x)*sum,kl.second-=sum,num-=sum;
if(kl.second) q2.push(kl);
sumsum+=sum,q1.push((PR){-kv-2*t[i].x,sum});
}
if(sumsum) q2.push((PR){-t[i].x-t[i].w,sumsum});
if(num) q1.push((PR){t[i].w-t[i].x,num});
}
}
}
int main()
{
n=read(),m=read();
for(RI i=1;i<=n;++i) t[i].x=read(),t[i].w=-1,t[i].c=0;
for(RI i=1;i<=m;++i)
t[i+n].x=read(),t[i+n].w=read(),t[i+n].c=read(),sumc+=t[i+n].c;
if(sumc<(LL)n) {puts("-1");return 0;}
sort(t+1,t+1+n+m,cmp),work();
printf("%lld\n",ans);
return 0;
}
若分身必须匹配一个,拆成两种,一种有一个,匹配后产生额外价值 − i n f -inf −inf(求最小价值),一种有 c i − 1 c_i-1 ci−1个,匹配后产生额外价值 0 0 0。
上树愉快!使用可并堆,自底向上合并,每一次合并将来自不同子树的箱子和传送点配一配。
复杂度还是不会证QAQ。
loj#6405 征服世界
#include
using namespace std;
#define RI register int
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;
}
typedef long long LL;
typedef pair<LL,int> PR;
const int N=250005;
const LL inf=1e12;
int n,tot,SZ;LL ans;
int h[N],ne[N<<1],to[N<<1],X[N],Y[N],rtX[N],rtY[N];LL dis[N],w[N<<1];
struct node{int ls,rs,d;PR v;}tr[N*30];
//申请超大结构体要花很多时间,建议拆成单个数组。
int merge(int a,int b) {
if(!a||!b) return a|b;
if(tr[a].v>tr[b].v) swap(a,b);
tr[a].rs=merge(tr[a].rs,b);
if(tr[tr[a].rs].d>tr[tr[a].ls].d) swap(tr[a].ls,tr[a].rs);
tr[a].d=tr[tr[a].rs].d+1;return a;
}
int newnode(LL val,int sum)
{++SZ,tr[SZ].v=(PR){val,sum},tr[SZ].d=1;return SZ;}
void add(int x,int y,LL z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void mer(int x,int y,LL d) {
while(rtX[x]&&rtY[y]) {
LL v1=tr[rtX[x]].v.first,v2=tr[rtY[y]].v.first;
if(v1+v2-2*d>=0) break;
int sum=min(tr[rtX[x]].v.second,tr[rtY[y]].v.second);
ans+=(v1+v2-2*d)*sum;
tr[rtX[x]].v.second-=sum,tr[rtY[y]].v.second-=sum;
if(!tr[rtX[x]].v.second) rtX[x]=merge(tr[rtX[x]].ls,tr[rtX[x]].rs);
if(!tr[rtY[y]].v.second) rtY[y]=merge(tr[rtY[y]].ls,tr[rtY[y]].rs);
rtX[x]=merge(rtX[x],newnode(-v2+2*d,sum));
rtY[y]=merge(rtY[y],newnode(-v1+2*d,sum));
}
}
void dfs(int x,int las) {
if(X[x]) rtX[x]=newnode(dis[x],X[x]);
if(Y[x]) rtY[x]=newnode(dis[x]-inf,Y[x]),ans+=1LL*Y[x]*inf;
for(RI i=h[x];i;i=ne[i]) {
int y=to[i];if(y==las) continue;
dis[y]=dis[x]+w[i],dfs(y,x);
mer(x,y,dis[x]),mer(y,x,dis[x]);
rtX[x]=merge(rtX[x],rtX[y]),rtY[x]=merge(rtY[x],rtY[y]);
}
}
int main()
{
int x,y,z;
n=read();
for(RI i=1;i<n;++i)
x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
for(RI i=1;i<=n;++i) {
X[i]=read(),Y[i]=read();
int kl=min(X[i],Y[i]);
X[i]-=kl,Y[i]-=kl;
}
dfs(1,0);
printf("%lld\n",ans);
return 0;
}