題意
有一條路可以看成是無盡頭的數軸
學生可以選擇一個點開始跑步,可以選擇從任意時間\(t_1\)開始跑,從任意時間\(t_2\)結束跑步,也可以選擇跑步方向,但跑步速度恆定為\(1\ m/s\)
跑步開始前不會出現在數軸上,跑步結束后也不會出現在數軸上
這條路上有一些監控,你收到了\(n\)份報告,每份報告有兩個數據\(x_i\)和\(t_i\),表示時間為\(t_i\)秒時在數軸的\(x_i\)位置有至少一個學生在跑步
問最少有多少個學生在跑步
思路
誰知道\(O(n^2m)\)的Dinic能在\(T_{max}=100,\ n_{max}=10^5\)的題里跑這么快?
完全不用考慮數據范圍,直接當二分圖最小頂點覆蓋/二分圖最大匹配模板題做
可以忽視題目中”跑步開始前不會出現在數軸上,跑步結束后也不會出現在數軸上“這句話
因為即使出現在數軸上,沒被監控拍到報告也就對題目無影響
而如果被監控拍到,那么就可以把符合他的跑步條件的所有報告全部看成只有他一個人在跑步(最優)使得總人數理論最少
假設某份報告表示\(t_i\)時刻在\(x_i\)位置有人跑步
那么這個人就當他至始至終都在跑步,那么有以下兩種可能:
一、他從\(x_i-t_i\)位置開始跑步,向數軸正方向跑
二、他從\(x_i+t_i\)位置開始跑步,向數軸負方向跑
那么對於這兩種情況,在\(xOt\)坐標系內,就相當於根據一個點\((x_i,t_i)\)畫出了兩條斜率分別為\(1\)和\(-1\)的直線
如下圖,如果\((x_i,t_i)\)對應\((2,2)\),則可以在\(xOt\)平面內畫出可能的兩條運動軌跡函數
對於題目中的樣例一,繪圖如下
所以此時題目就轉變成了:選擇最少的直線,能夠包括所有給定的點\((x_i,t_i)\)
發現就是一道二分圖最小頂點覆蓋的模板題
可以將斜率為\(1\)和\(-1\)的兩類曲線當作二分圖中\(U\)集和\(V\)集,如果存在某個點\((x_i,t_i)\),則將\(x_i-t_i\)放入\(U\)集,將\(x_i+t_i\)放入\(V\)集,並在兩者之間連一條邊
如果選擇了某個點,就把所有與其相鄰的邊從圖中去掉,所以求的就是最小頂點覆蓋
完整程序
下面直接使用dinic跑二分圖最小頂點覆蓋
建圖為:
原點連向所有形如\(x_i-t_i\)的點,流量為1
所有形如\(x_i-t_i\)的點連向匯點,流量為1
所有形如\(x_i-t_i\)的點連向形如\(x_i-t_i\)的點,流量為\(\infin\)
由於數據達\(10^9\),需要先離散化
同理使用其余解決二分圖最小頂點覆蓋/二分圖最大匹配的算法基本都能過(例如匈牙利)
未了解過二分圖的可以瞅瞅【圖論】二分圖/二分圖最大匹配/二分圖最大權完美匹配呀
(2386ms/5000ms)
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=333333,maxm=666666;
struct edge{
int u,v,cap,flow;
edge(){}
edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];
void init(int n,int s_,int t_){
while(n)tab[n--].clear();
tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
tab[u].push_back(tot);
eg[tot++]=edge(u,v,cap,0);
tab[v].push_back(tot);
eg[tot++]=edge(v,u,0,0);
}
int bfs(){
queue<int> q;
q.push(s);
memset(dis,INF,sizeof dis);
dis[s]=0;
while(!q.empty()){
int h=q.front(),i;
q.pop();
for(i=0;i<tab[h].size();i++){
edge &e=eg[tab[h][i]];
if(e.cap>e.flow&&dis[e.v]==INF){
dis[e.v]=dis[h]+1;
q.push(e.v);
}
}
}
return dis[t]<INF;
}
int dfs(int x,int maxflow){
if(x==t|maxflow==0)
return maxflow;
int flow=0,i,f;
for(i=cur[x];i<tab[x].size();i++){
cur[x]=i;
edge &e=eg[tab[x][i]];
if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
e.flow+=f;
eg[tab[x][i]^1].flow-=f;
flow+=f;
maxflow-=f;
if(maxflow==0)
break;
}
}
return flow;
}
int dinic(){
int flow=0;
while(bfs()){
memset(cur,0,sizeof(cur));
flow+=dfs(s,INF);
}
return flow;
}
int ar[222222],cnt;
int X[111111],T[111111];
void solve()
{
int n;
cin>>n;
cnt=0;
for(int i=1;i<=n;i++)
{
cin>>T[i]>>X[i];
ar[++cnt]=X[i]-T[i];
ar[++cnt]=X[i]+T[i];
}
sort(ar+1,ar+1+cnt);
cnt=unique(ar+1,ar+1+cnt)-ar-1;
init(2*cnt+2,2*cnt+1,2*cnt+2);
for(int i=1;i<=n;i++)
{
int a=lower_bound(ar+1,ar+1+cnt,X[i]-T[i])-ar;
int b=lower_bound(ar+1,ar+1+cnt,X[i]+T[i])-ar;
addedge(a,b+cnt,INF);
}
for(int i=1;i<=cnt;i++)
addedge(s,i,1),addedge(i+cnt,t,1);
cout<<dinic()<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
solve();
return 0;
}