貪心相關/模擬網絡流、費用流細節梳理/模板(貪心,模擬費用流,棧)

去不了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'(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;
}

基礎的費用流模型:左邊老鼠,右邊洞,中間數軸。一單位流相當於一個匹配。

模擬費用流做法:坑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章