CF1178H Stock Exchange
Link
首先给出一个结论:
对于时间为\(t\)的最优方案\(P\),一定存在另一组时间为\(t\)的最优方案\(P'\),满足\(P'\)只在\(0,t\)两个时间点交换,且\(P'\)的交换数等于\(P\)。
我们可以把每个股票看作是一个一次函数,而我们在\(t\)可以\(a\)交换\(b\)的充要条件就是\(y_a|_{x=t}\ge y_b|_{x=t}\)。
我们可以将方案\(P\)中的交换看作是从\(1,\cdots,n\)到\(n+1,\cdots,2n\)的一组匹配。
每组匹配\(i\rightarrow j\)只有几种情况:
若\(a_j\le a_i\),则在\(0\)时刻直接从\(i\)交换到\(j\)。
若\(a_j>a_i\),则此时一定有\(b_j<b_i\)。
如果\(y_i|_{x=t}\ge y_j|_{x=y}\),那么就直接等到\(t\)时刻交换。
否则我们在\(0\)时刻交换到\(a_k\le a_i\)且\(b_k\)最大的\(k\)直线,然后在\(t\)时刻交换到\(j\)。
如果上面几种情况都不合法的话那么这组匹配就是不可能存在的。
那么我们可以先二分答案\(t\),检查在\(t\)时刻能否完成所有交换。
因为我们只需要检查可行性,并不需要最小化步数,所以我们可以在\(0\)时刻把所有\(i\)都交换到\(a_k\le a_i\)且\(y_k|_{x=t}\)最大的\(k\)直线,再排个序就可以直接贪心匹配了。
这样我们就成功地求出了最小的时间,接下来考虑求解最小交换次数。
很显然我们有一个边数为\(n^2\)的暴力最小费用流做法,即在\(0,t\)两个时刻每条直线向当前时刻在自己下面的直线连一条容量费用皆为\(1\)的边。但这看上去就不可能过。
注意到按当前时刻的高度升序排序之后,每条直线连向的边都是一段前缀,因此前缀和优化连边即可。
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
struct node{int u,v,f,c,p;node(int a=0,int b=0,int d=0,int e=0,int g=0):u(a),v(b),f(d),c(e),p(g){}};
using i64=long long;
using IT=std::vector<node>::iterator;
const int N=14007;
int n,s,t,dis[N],flow[N],inq[N];IT id[N];
struct line{int k,b,id;}a[N];
std::queue<int>q;std::vector<node>e[N];
int read(){int x;scanf("%d",&x);return x;}
int check(i64 t)
{
static i64 r[N],s[N];memset(r+1,0,n<<3);
for(int i=1;i<=n;++i) for(int j=1;j<=n+n;++j) if(a[i].b>=a[j].b) r[i]=std::max(r[i],t*a[j].k+a[j].b);
for(int i=1;i<=n;++i) s[i]=t*a[n+i].k+a[n+i].b;
std::sort(r+1,r+n+1),std::sort(s+1,s+n+1);
for(int i=1;i<=n;++i) if(r[i]<s[i]) return 0;
return 1;
}
void add(int u,int v,int f,int c){e[u].emplace_back(u,v,f,c,(int)e[v].size()),e[v].emplace_back(v,u,0,-c,(int)e[u].size()-1);}
int spfa()
{
memset(dis,0x3f,sizeof dis),memset(flow,0x3f,sizeof flow),q.push(s),inq[s]=1,dis[s]=0,id[t]=e[t].begin();
for(int u,v;!q.empty();)
{
u=q.front(),q.pop(),inq[u]=0;
for(IT it=e[u].begin();it!=e[u].end();++it)
if(it->f&&dis[v=it->v]>dis[u]+it->c)
{
dis[v]=dis[u]+it->c,id[v]=it,flow[v]=std::min(flow[u],it->f);
if(!inq[v]) q.push(v),inq[v]=1;
}
}
return id[t]!=e[t].begin();
}
void build(i64 tim)
{
s=6*n+1,t=6*n+2;
for(int i=1;i<=n;++i) add(s,i+n*4,1,0),add(i+n*4,i,1,1),add(i+n*4,i+n+n,1,0),add(i+n*5,t,1,0),add(i+n*3,i+n*5,1,1),add(i+n,i+n*5,1,0);
for(int i=1;i<=n+n;++i) add(i,i+n+n,10000,0);
std::sort(a+1,a+n+n+1,[](line a,line b){return a.b>b.b;});
for(int l=1,r;l<=n+n;l=r+1)
{
for(r=l;a[r+1].b==a[l].b;++r);
for(int i=l;i<=r;++i) if(i<n+n) add(a[i].id,a[i+1].id,10000,0);
add(a[r].id,a[l].id,10000,0);
}
std::sort(a+1,a+n+n+1,[&](line a,line b){return tim*a.k+a.b>tim*b.k+b.b;});
for(int l=1,r;l<=n+n;l=r+1)
{
for(r=l;tim*a[r+1].k+a[r+1].b==tim*a[l].k+a[l].b;++r);
for(int i=l;i<=r;++i) if(i<n+n) add(a[i].id+n+n,a[i+1].id+n+n,10000,0);
add(a[r].id+n+n,a[l].id+n+n,10000,0);
}
}
int EK()
{
int ans=0;
for(int p;spfa();) for(p=t,ans+=flow[t]*dis[t];p^s;p=id[p]->u) id[p]->f-=flow[t],e[p][id[p]->p].f+=flow[t];
return ans;
}
int main()
{
n=read();int ans=0;
for(int i=1;i<=n+n;++i) a[i]={read(),read(),i};
if(!check(1e9)) return !printf("-1");
for(int l=0,r=1e9,mid;l<=r;) check(mid=(l+r)>>1)? (ans=mid,r=mid-1):(l=mid+1);
build(ans),printf("%d %d",ans,EK());
}