「AGC031E」 Snuke the Phantom Thief 題解
阿新 • • 發佈:2022-03-16
「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\)
\(~~~~\) 那考慮對於每條限制:假設規定橫座標 \(\leq a_i\) 的珠寶最多取 \(b_i\) 個,那不就等價於橫座標 \(>a_i\) 的珠寶最少取 \(k-b_i\) 個嗎?那人為使選擇的珠寶按橫座標升序排序,這樣就可以確定每個珠寶的橫座標範圍,同理也可以確定縱座標,注意這裡不需要保證橫座標和縱座標要繫結在同一珠寶上。
\(~~~~\) 注意到還有每個寶石只能選一次的限制,那麼就將每個實際寶石的點拆成入和出,中間連一條 \(\text{Flow}=1,\text{Cost}=val_i\) 的邊。
\(~~~~\)
程式碼
檢視程式碼
#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; }