• 「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;
    }
    
  • 相关阅读:
    在新浪爱问上看到的有趣名字都记下来
    FastReports_4.14.1 _Cliff手动安装
    计算机算法对做事效率的启发(既要高强度猛攻,也要细水长流)
    有趣的数组
    listView 分页加载数据
    如何隐藏Cognos Viewer
    使用DBUnit实现对数据库的测试
    通过Jasmine和Guard自动测试JavaScript
    for惠普2013实习生
    栈的顺序存储,所谓的顺序栈
  • 原文地址:https://www.cnblogs.com/Azazel/p/16014860.html
Copyright © 2020-2023  润新知