zoukankan      html  css  js  c++  java
  • 贪心相关/模拟网络流、费用流细节梳理/模板(贪心,模拟费用流,栈)

    去不了WC的蒟蒻只能orz laofu qaq

    参考

    %YCB%

    题单

    【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 雪灾与外卖

    部分思路

    直接通过建图的特殊性质加速增广

    Museum

    看到保护关系可以直接想到最大权闭合子图。建图,左边每个手办向右边能看到它的警卫连边,跑最小割。

    对坐标系进行变换(大概是放缩横坐标使得警卫的视角是(90°),再整体旋转(45°)),可以看到连边实际上是一个二维偏序关系。

    树套树优化网络流

    显然要离线其中一维做一个扫描线,以降低一个(log)的复杂度。扫到一个手办的时候就尝试增广。

    显然增广时优先走第二维刚好比它大一点的警卫,这样是不用退流的。set按第二维排序维护还没流满的警卫即可。

    注意比较函数的写法,防止插入的时候被判等而插入失败。好像写multiset也行。

    #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;
    }
    

    老鼠进洞模型

    数轴上,某些位置有老鼠或洞,洞有容量,所有老鼠都要进洞,代价为移动距离。最小化代价。

    先假设洞容量为(1)

    DP的做法:给老鼠和洞按位置从左到右依次编号。设(f_{i,j})表示到第(i)个位置有(j)个老鼠未匹配的最小代价((j)可以为负,表示还需要(j)个老鼠),那么最后的(f_{n,0})是答案。

    把代价拆开,(i)(i'(ile i'))匹配的代价是(x_{i'}-x_i),只需要在(i)处减(x_i)、在(i')处加(x_{i'})

    (i)是老鼠:(f_{i,j}=f_{i-1,j-1}+egin{cases}-x_i(j>0)\+x_i(jle0)end{cases}),看起来好做。

    (i)是洞:(f_{i,j}=minleft(f_{i,j},f_{i-1,j+1}+egin{cases}-x_i(j<0)\+x_i(jge0)end{cases} ight)),看起来不好做。

    但实际上有用的转移:(f_{i,j}=egin{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}),元素不够的时候补( ext{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;
    }
    

    基础的费用流模型:左边老鼠,右边洞,中间数轴。一单位流相当于一个匹配。

    模拟费用流做法:坑

  • 相关阅读:
    Mac下ssh连接远程服务器时自动断开问题
    解决php中json_decode的异常JSON_ERROR_CTRL_CHAR (json_last_error = 3)
    如何写.gitignore只包含指定的文件扩展名
    python操作mysql数据库
    php数组函数
    Python中字符串切片操作
    Python实现字符串反转的几种方法
    每个Android开发者都应该了解的资源列表
    Android Studio 入门指南
    一个优秀的Android应用从建项目开始
  • 原文地址:https://www.cnblogs.com/flashhu/p/10597795.html
Copyright © 2011-2022 走看看