题意:给定n组点,表示在t分钟,x处有人出现。询问最少有多少人
每个人都可以从任意地点朝任意方向出发,每分钟走一步。
题解:
对于这道题,初始可能有一个想法,将这点放在坐标轴上,有两种直线可以选取,一种是斜率为-1的,一种是斜率为1的
我们要用这两种直线来覆盖所有的点,问的是直线个数的最小值。如果光这个思考,难度还是比较大的,图论问题最难的就是建模。
我们再抽象一下,因为方向任意,所以任意一个点都可以从x-t或者x+t,这两个位置转移过来,对于每个点都如此,显然我们可以把他看作是两个集合
一个集合表示向左走,一个集合表示向右走,因此我们可以把点看作边,而左右位置看作点,只要这个边的任意一个端点被选取即可
这样就转化成了二分图最小点覆盖问题,等价于二分图最大匹配,在连边的时候,注意去重,因为二分图最大匹配当中两个点之间只能有一条边,但是重边不影响答案。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=5e5+10; const int inf=0x3f3f3f3f; const int mod=1e9+7; int n,S,T; int h[N],ne[N],e[N],f[N],idx; map<ll,int> m1[2]; set<pll> s; int d[N],cur[N]; void init(){ int i; for(i=0;i<=2*n+1;i++){ h[i]=-1; } idx=0; m1[0].clear(); m1[1].clear(); s.clear(); } void add(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],f[idx]=c,h[a]=idx++; e[idx]=a,ne[idx]=h[b],f[idx]=0,h[b]=idx++; } int bfs(){ memset(d,-1,sizeof d); d[S]=0; cur[S]=h[S]; queue<int> q; q.push(S); while(q.size()){ int t=q.front(); q.pop(); for(int i=h[t];i!=-1;i=ne[i]){ int j=e[i]; if(d[j]==-1&&f[i]){ cur[j]=h[j]; d[j]=d[t]+1; if(j==T) return true; q.push(j); } } } return false; } int find(int u,int limit){ if(u==T){ return limit; } int i; int flow=0; for(i=cur[u];i!=-1&&flow<limit;i=ne[i]){ cur[u]=i; int j=e[i]; if(d[j]==d[u]+1&&f[i]){ int t=find(j,min(f[i],limit-flow)); if(!t) d[j]=-1; else{ f[i]-=t; f[i^1]+=t; flow+=t; } } } return flow; } int dinic(){ int flow; int r=0; while(bfs()){ while(flow=find(S,inf)) r+=flow; } return r; } void solve(){ cin>>n; init(); int cnt1=0,cnt2=0; int i; S=0,T=2*n+1; for(i=1;i<=n;i++){ ll xi,ti; cin>>ti>>xi; ll tmp1=xi-ti,tmp2=xi+ti; if(!m1[0].count(tmp1)) m1[0][tmp1]=++cnt1; if(!m1[1].count(tmp2)) m1[1][tmp2]=++cnt2; int id1=m1[0][tmp1]; int id2=m1[1][tmp2]; if(s.find({id1,id2})==s.end()){ s.insert({id1,id2}); add(id1,n+id2,1); } } for(i=1;i<=cnt1;i++){ add(S,i,1); } for(i=1;i<=cnt2;i++){ add(n+i,T,1); } cout<<dinic()<<endl; } int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ solve(); } }