【UER #8】雪灾与外卖
题解:
这个东西的模型是个费用流
但是直接跑费用流能拿到5分的高分
$(nm)*(nm)*log{nm}$
考虑优化一下建图
我们可以不用对每个店和人都连边
而是对人和店都连一条链
然后对每个人连店刚好比它大和比它小的两个点
这样有25分
我们容易发现每个店的区间都是连续并且递增的
于是可以dp
单调队列优化之后就可以有50分
再之后的做法就比较神奇
考虑先将每个店容量拆成1
我们将店和人按照坐标排序
由于每个人都是要匹配的,所以我们可以直接将它和前面的匹配(如果没有看成和无限远处匹配)
然后当我们加入一家店时,我们看一看有没有人换成和它匹配更优,没有就直接插入
具体如何实现呢
我们对店和人分别维护一个堆
一个人去匹配店的时候,产生的代价为$x+y$ (x为坐标,y为店堆顶)
匹配完后我们要支持人的反悔操作(容易yy店是不可能反悔的)
也就是往人的堆里插入$-(x+y)-x$
一个店去匹配人的时候,产生的代价为$x+y+w$(y为坐标,w为花费,x为人堆顶)
匹配完后要支持人和店的反悔
人的反悔是$-(y+x坐标+w)-x坐标$=$-y-w$
店的反悔我们可以通过推一下反悔后的式子(注意反悔时我们要将它现在匹配的那个人匹配的店变回之前匹配的店)$-x-2*y$
刚开始在店1 人1 店2 人2 店3 店4(假设店对两个人的花费都是从大到小)
这个例子上纠结了一下(人和店1的匹配被弹栈了之后怎么办)
后来发现 如果出现店1 人1 店2的话(且店2比店1优) 一定不存在一种时候可以直接从店1 人1的匹配变成人1和其他店
因为如果后面有店一定先让人2匹配,而一旦人2匹配了人1就匹配了店2
然后考虑一下满分做法
没有了ci的限制 我们就不能拆了 对于人的操作 时间复杂度是对的
而对于物品的操作,时间复杂度的瓶颈在于对每个人可能每个位置的店都要算一次
我们发现对于同一个位置的店的人的反悔操作,我们可以放在一起,都只跟店有关
而对于多余的店,也可以放在一起
因为每次操作只能使块数增加1 所以最多减少n+m次
复杂度$(n+m)log$
我是照着这份代码看懂的。。
#include<bits/stdc++.h> using namespace std; #define FOR(a,b,c) for(int a=(b),a##_end__=(c);a<a##_end__;++a) #define INF 0x3f3f3f3f3f3f3f3fLL template<class T>inline bool chkmin(T&a,T const&b){return b<a?a=b,true:false;} template<class T>inline bool chkmax(T&a,T const&b){return a<b?a=b,true:false;} const int M=100005; priority_queue<pair<long long,int>,vector<pair<long long,int>>,greater<pair<long long,int>>>A,B; int X[M],Y[M],W[M],C[M]; long long ans,cnt; int n,m; void push_x(long long x){ long long y; if(B.empty()) y=INF; else y=B.top().first; ans+=x+y; A.push(make_pair(-2*x-y,1)); if(!B.empty()){ int f=B.top().second; B.pop(); if(1<f) B.push(make_pair(y,f-1)); } } void push_y(long long y,long long w,int c){ int m=0; while(c!=m and !A.empty() and y+w+A.top().first<0){ long long x=A.top().first; int f=A.top().second,t=min(c-m,f); ans+=t*(x+y+w); A.pop(); if(t!=f) A.push(make_pair(x,f-t)); B.push(make_pair(-x-2*y,t)); m+=t; } if(m) A.push(make_pair(-y-w,m)); if(c!=m) B.push(make_pair(-y+w,c-m)); } int main(){ freopen("1.in","r",stdin); freopen("1.out","w",stdout); scanf("%d%d",&n,&m); FOR(i,0,n) scanf("%d",X+i); FOR(i,0,m) scanf("%d%d%d",Y+i,W+i,C+i); FOR(i,0,m) cnt+=C[i]; if(cnt<n) return puts("-1"),0; int i=0,j=0; while(i<n and j<m){ if(X[i]<Y[j]) push_x(X[i]),++i; else push_y(Y[j],W[j],C[j]),++j; } while(i<n) push_x(X[i]),++i; while(j<m) push_y(Y[j],W[j],C[j]),++j; printf("%lld\n",ans); return 0; }