1. 程式人生 > 其它 >「AGC031E」 Snuke the Phantom Thief 題解

「AGC031E」 Snuke the Phantom Thief 題解

「AGC031E」Snuke the Phantom Thief

題意

\(~~~~\) \(n\) 顆珠寶,有座標 \((x_i,y_i)\) 和價值 \(val_i\) ,同時還有 \(m\) 條限制,每條限制規定在某條水平/豎直線某一側最多能取多少珠寶,求能取珠寶的最大價值。

\(~~~~\) \(1\leq n\leq 80,1\leq m\leq 320\).

題解

\(~~~~\) 輸在了第一步的網路流,看完題解豁然開朗的網路流。

\(~~~~\) 首先注意到 \(n,m\) 很小,貪心、DP之類也不可做,那就考慮網路流。

\(~~~~\) 然後就是上文提到的輸在第一步:列舉最終取了多少顆珠寶,將這個值記為 \(k\)

。這麼做是因為這道題不適合用流量去滿足 \(m\) 條限制。

\(~~~~\) 那考慮對於每條限制:假設規定橫座標 \(\leq a_i\) 的珠寶最多取 \(b_i\) 個,那不就等價於橫座標 \(>a_i\) 的珠寶最少取 \(k-b_i\) 個嗎?那人為使選擇的珠寶按橫座標升序排序,這樣就可以確定每個珠寶的橫座標範圍,同理也可以確定縱座標,注意這裡不需要保證橫座標和縱座標要繫結在同一珠寶上。

\(~~~~\) 注意到還有每個寶石只能選一次的限制,那麼就將每個實際寶石的點拆成入和出,中間連一條 \(\text{Flow}=1,\text{Cost}=val_i\) 的邊。

\(~~~~\)

然後建圖:從源點向代表橫座標的選擇寶石連 \(\text{Flow}=1,\text{Cost}=0\) 的邊(以下簡記為 \((1,0)\)),然後從代表橫座標的選擇寶石向符合要求的實際寶石的入點\((1,0)\)符合要求的實際寶石的出點代表縱座標的選擇寶石連邊。最後跑最大費用最大流即可。由於會優先保證 \(\text{Flow}\) 最大 ,所以一定會選出 \(\leq k\) 塊寶石,其中 \(<k\) 的情況在之前也統計過了,所以正確性有保證。

程式碼

檢視程式碼
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
struct Node{
	ll x,y,val,id;
	Node(){}
	Node(ll X,ll Y,ll Val,ll ID){x=X,y=Y,val=Val,id=ID;}
}Jew[85];
struct Limit{
	ll op,a,b;
	Limit(){}
	Limit(ll Op,ll A,ll B){op=Op,a=A,b=B;}
}Lim1[355],Lim2[355];
ll n,m;char op[1000];
ll Turn[300],cnt1,cnt2,L[85],R[85],U[85],D[85];
ll s,t,head[100005],to[1000005],nxt[1000005],W[1000005],Edgecnt;
ll Cost[1000005];
void AddEdge(ll a,ll b,ll c,ll d)
{
//	printf("%lld %lld,Flow=%lld,Cost=%lld\n",a,b,c,d);
	to[Edgecnt]=b; nxt[Edgecnt]=head[a]; W[Edgecnt]=c; Cost[Edgecnt]=d; head[a]=Edgecnt++;
	to[Edgecnt]=a; nxt[Edgecnt]=head[b]; W[Edgecnt]=0; Cost[Edgecnt]=-d;head[b]=Edgecnt++;
}
ll pre[100005],Flow[100005],Inqueue[100005];
ll Dis[100005];
ll SPFA()
{
	queue<ll>q;
	for(ll i=1;i<=t;i++) Flow[i]=0,Dis[i]=-1e18,Inqueue[i]=0,pre[i]=-1;
	q.push(s); Dis[s]=0; Flow[s]=1e18; pre[s]=0;
	while(!q.empty())
	{
		ll u=q.front();q.pop();
		Inqueue[u]=false;
		for(ll i=head[u];~i;i=nxt[i])
		{
			ll v=to[i];
			if(!W[i]||Dis[v]>=Dis[u]+Cost[i]) continue;
			Dis[v]=Dis[u]+Cost[i]; Flow[v]=min(Flow[u],W[i]);
			pre[v]=i;
			if(!Inqueue[v]){q.push(v),Inqueue[v]=true;}
		}
	}
	if(Dis[t]==-1e18) return 0;
	return Flow[t];
}
ll CMFM()
{
	ll tmp=0;ll ret=0;
	while((tmp=SPFA()))
	{
		ret+=1ll*Dis[t]*tmp;
		ll x=t;
		while(x!=s)
		{
			W[pre[x]]-=tmp,W[pre[x]^1]+=tmp;x=to[pre[x]^1];
		}
	}
	return ret;
}
ll Solve(ll k)
{
	memset(head,-1,sizeof(head)); Edgecnt=0;
	memset(L,128,sizeof(L)); memset(D,128,sizeof(D));
	memset(R,127,sizeof(R)); memset(U,127,sizeof(U));
	s=2*(k+n)+1;t=s+1;
//	sort(Jew+1,Jew+1+n);
	for(ll i=1;i<=k;i++) AddEdge(s,i,1,0),AddEdge(i+k+2*n,t,1,0);
	for(ll i=1;i<=n;i++) AddEdge(k+Jew[i].id,k+n+Jew[i].id,1,Jew[i].val);
	for(ll i=1;i<=cnt1;i++)
	{
		ll p=Lim1[i].b;
		if(Lim1[i].op==1)//左邊至多 p 個 => 右邊至少 k-p 個 =>後 k-p 個的左端點必定 > a_i 
			for(ll j=k;j>=p+1;j--) L[j]=max(Lim1[i].a+1,L[j]);
		else //同上,前 k-p 個右端點 < a_i 
			for(ll j=1;j<=k-p;j++) R[j]=min(Lim1[i].a-1,R[j]); 
	}
	for(ll i=1;i<=cnt2;i++)
	{
		ll p=Lim2[i].b;
		if(Lim2[i].op==1)
			for(ll j=k;j>=k-(k-p)+1;j--) D[j]=max(Lim2[i].a+1,D[j]);
		else
			for(ll j=1;j<=k-p;j++) U[j]=min(Lim2[i].a-1,U[j]);
	}
	for(ll i=1;i<=k;i++)
	{
		for(ll j=1;j<=n;j++)
		{
			if(L[i]<=Jew[j].x&&Jew[j].x<=R[i]) AddEdge(i,k+j,1,0);
			if(D[i]<=Jew[j].y&&Jew[j].y<=U[i]) AddEdge(k+n+j,k+2*n+i,1,0);
		}
	}
//	puts("===");
	return CMFM();
}
int main() {
	read(n);ll val;
	for(ll i=1,x,y;i<=n;i++)
	{
		read(x); read(y); read(val);
		Jew[i]=Node(x,y,val,i);
	}
	read(m);
	Turn['L']=1; Turn['R']=2;
	Turn['D']=1; Turn['U']=2;
	for(ll i=1,a,b;i<=m;i++)
	{
		scanf("%s",op+1); read(a); read(b);
		if(op[1]=='L'||op[1]=='R') Lim1[++cnt1]=Limit(Turn[(int)op[1]],a,b);
		else Lim2[++cnt2]=Limit(Turn[(int)op[1]],a,b);
	}
	ll Ans=0;
	for(ll k=1;k<=n;k++) 
		Ans=max(Ans,Solve(k));
	printf("%lld",Ans);
	return 0;
}