去不了WC的蒟蒻只能orz laofu qaqphp
%YCB%html
【Done】牛客挑战赛7F Masha与老鼠
【Todo】洛谷P2514 HAOI2010工厂选址
【Done】洛谷P3826 NOI2017蔬菜
【Todo】洛谷AT3687 Farm Village
【Todo】洛谷CF280D k-Maximum Subsequence Sum
【Todo】BZOJ2034 最大收益
【Done】BZOJ3716 PA2014Muzeum
【Done】BZOJ4849 Mole Tunnels(到这里交)
【Done】BZOJ4977 跳伞求生
【Todo】LOJ6405 征服世界
【Todo】UOJ455 雪灾与外卖c++
看到保护关系能够直接想到最大权闭合子图。建图,左边每一个手办向右边能看到它的警卫连边,跑最小割。网络
对坐标系进行变换(大概是放缩横坐标使得警卫的视角是\(90°\),再总体旋转\(45°\)),能够看到连边其实是一个二维偏序关系。函数
树套树优化网络流优化
显然要离线其中一维作一个扫描线,以下降一个\(\log\)的复杂度。扫到一个手办的时候就尝试增广。spa
显然增广时优先走第二维恰好比它大一点的警卫,这样是不用退流的。set按第二维排序维护还没流满的警卫便可。code
注意比较函数的写法,防止插入的时候被判等而插入失败。好像写multiset也行。htm
#include<bits/stdc++.h> #define LL long long #define R register int #define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin)) using namespace std; const int SZ=1<<19,N=2e5+9; char buf[SZ],*ie=buf+SZ,*ip=ie-1; inline int in(){ G;while(*ip<'-')G; R f=*ip=='-';if(f)G; R x=*ip&15;G; while(*ip>'-'){x*=10;x+=*ip&15;G;} return f?-x:x; } int n,m;LL w,h,ans; struct Dat{ int x,y,v; inline bool operator<(const Dat&a)const{ return (y-a.y)*w>(a.x-x)*h; }//第一维排序 }a[N],b[N]; struct Cmp{ inline bool operator()(Dat*a,Dat*b){ LL u=(a->y-b->y)*w,v=(a->x-b->x)*h; return u<v||(u==v&&a<b); }//第二维排序 }; set<Dat*,Cmp>s; int main(){ n=in(),m=in(),w=in(),h=in(); for(R i=0;i<n;++i)a[i].x=in(),a[i].y=in(),ans+=a[i].v=in(); for(R i=0;i<m;++i)b[i].x=in(),b[i].y=in(),b[i].v=in(); sort(a,a+n); sort(b,b+m); for(R i=0,j=0;i<n;++i){ for(;j<m&&!(a[i]<b[j]);s.insert(b+j++)); set<Dat*,Cmp>::iterator it=s.lower_bound(a+i); for(;it!=s.end()&&(*it)->v<=a[i].v;s.erase(it++)) ans-=(*it)->v,a[i].v-=(*it)->v;//手动修改剩余容量 if(it!=s.end()) ans-=a[i].v,(*it)->v-=a[i].v; } cout<<ans; return 0; }
数轴上,某些位置有老鼠或洞,洞有容量,全部老鼠都要进洞,代价为移动距离。最小化代价。blog
先假设洞容量为\(1\)。
DP的作法:给老鼠和洞按位置从左到右依次编号。设\(f_{i,j}\)表示到第\(i\)个位置有\(j\)个老鼠未匹配的最小代价(\(j\)能够为负,表示还须要\(j\)个老鼠),那么最后的\(f_{n,0}\)是答案。
把代价拆开,\(i\)与\(i'(i\le i')\)匹配的代价是\(x_{i'}-x_i\),只须要在\(i\)处减\(x_i\)、在\(i'\)处加\(x_{i'}\)。
当\(i\)是老鼠:\(f_{i,j}=f_{i-1,j-1}+\begin{cases}-x_i(j>0)\\+x_i(j\le0)\end{cases}\),看起来好作。
当\(i\)是洞:\(f_{i,j}=\min\left(f_{i,j},f_{i-1,j+1}+\begin{cases}-x_i(j<0)\\+x_i(j\ge0)\end{cases}\right)\),看起来很差作。
但实际上有用的转移:\(f_{i,j}=\begin{cases}f_{i-1,j+1}-x_i(j<0)\\f_{i-1,j+1}+x_i(j>0)\\\min(f_{i,j},f_{i-1,j+1}+x_i)(j=0)\end{cases}\)
为何只剩下\(j=0\)时候须要决策了呢?显然可能出现洞多了不会选的状况。问题在于其它的转移的决策怎么没了。laofu惜字如金,蒟蒻只好感性理解了:大概是由于\(x_i\)是递增的,因此以前转移完,相邻下标的两个\(f\)的差不大于如今的\(x_i\),当前转移完仍是知足这一条件。
因而,这两种转移,在\(j<0\)和\(j>0\)的部分,都是在把序列总体移动,全局加一个值,再推入/删除边上的元素。使用两个栈维护,栈顶分别是\(f_1,f_{-1}\),元素不够的时候补\(\text{INF}\)就好了。\(f_0\)单独维护。
再讨论容量任意的状况。
一个洞容量可能会很大,但永远只会有它周围的老鼠进去。
假设老鼠只往左走,算每一个洞会进多少。再假设老鼠只往右走,算每一个洞会进多少。每一个洞的容量对这两个值的和取\(\min\)。
%laofu的证实吧:
Proof.
从第一遍贪心到原问题,对于每个洞而言,从它右边出发到达它左边的老鼠不会变多。
从第二遍贪心到原问题,对于每个洞而言,从它左边出发到达它右边的老鼠不会变多。
而后总容量就不超过\(2n\)了,跟上面同样作。
写基排的话时间复杂度就是\(O(n)\)。
#include<bits/stdc++.h> #define LL long long #define UC unsigned char #define R register int #define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin)) using namespace std; const int SZ=1<<19,N=1e6+9;const LL INF=1e18; char buf[SZ],*ie=buf+SZ,*ip=ie-1; inline int in(){ G;while(*ip<'-')G; R f=*ip=='-';if(f)G; R x=*ip&15;G; while(*ip>'-'){x*=10;x+=*ip&15;G;} return f?-x:x; } template<typename T>//基排 void Rsort(T*fst,T*lst,T*buf,int*op){ static int b[0x100]; int Len=lst-fst,Sz=sizeof(T),at=0,i,j; UC*bgn,*end,tmp; for(i=0;i<Sz;++i){ if(op[i]==-1)continue; bgn=(UC*)fst+i;end=(UC*)lst+i; tmp=((op[i]&1)?0xff:0)^((op[i]&2)?0x80:0); memset(b,0,sizeof(b)); for(UC*it=bgn;it!=end;it+=Sz)++b[tmp^*it]; for(j=1;j<0x100;++j)b[j]+=b[j-1]; for(UC*it=end;it!=bgn;buf[--b[tmp^*(it-=Sz)]]=*--lst); lst=buf+Len;swap(fst,buf);at^=1; } if(at)memcpy(buf,fst,Sz*Len); } int a[N],aa[N]; struct Hole{int p,c;}b[N],bb[N]; LL s[5*N],t[5*N]; int main(){ R n=in(),m=in();LL f=0,g=0,h=0; for(R i=1;i<=n;++i)a[i]=in(); for(R i=1;i<=m;++i)b[i].p=in(),b[i].c=in(); Rsort(a+1,a+n+1,aa,new int[4]{0,0,0,2}); Rsort(b+1,b+m+1,bb,new int[8]{0,0,0,2,-1,-1,-1,-1}); for(R i=1,j=1,x=0;i<=m;++i){//两遍贪心控制容量范围 for(;j<=n&&a[j]<=b[i].p;++j,++x); x-=aa[i]=min(x,b[i].c); } for(R i=m,j=n,x=0,y;i;--i){ for(;j&&a[j]>=b[i].p;--j,++x); y=aa[i]; x-=aa[i]=min(x,b[i].c); b[i].c=min(b[i].c,y+aa[i]); } b[++m].p=1e9; for(R i=1,j=1,p=0,q=0;i<=m;++i){//双栈维护DP for(;j<=n&&a[j]<=b[i].p;++j){ s[++p]=f-g;g-=a[j]; if(!q)t[++q]=INF; f=t[q--]+(h+=a[j]); } while(b[i].c--){ t[++q]=f-h;h-=b[i].p; if(!p)s[++p]=INF; f=min(f,s[p--]+(g+=b[i].p)); } } return cout<<(f>1e17?-1:f),0; }
基础的费用流模型:左边老鼠,右边洞,中间数轴。一单位流至关于一个匹配。
模拟费用流作法:坑