【BZOJ4200】【NOI2015】小园丁与老司机(动态规划,网络流)
【BZOJ4200】【NOI2015】小园丁与老司机(动态规划,网络流)
题面
题解
一道二合一的题目
考虑第一问。
先考虑如何计算六个方向上的第一个点。
左右上很好考虑,只需要按照\(x\)或者\(y\)轴排序就行了。
对于\(45\)度的斜角,两点一定在同一条直线上。
这条直线是\(x+y=b\)或\(x-y=b\)
所以按照\(x+y\)和\(x-y\)的值分类考虑,再按照顺序在\(x\)轴扫一遍就可以找到了。
考虑如何计算第一问的答案,我们发现\(y\)轴是单调不降的。
所以可以以\(y\)来划分截断,按照\(y\)轴从上往下进行转移。
设\(f[i]\)表示\(i\)的答案,对于所有\(y\)值相同的点放在一起解决。
每次转移的时候,先将当前\(y\)相同的所有点通过左上右上和上三个方向进行转移。
然后再考虑左右的转移。因为左右转移是可以先向一个方向绕过去再走回来的,
所以考虑转移的时候需要额外注意一下。
所以再诶外设一个\(g\),\(f[i]\)表示从\(i\)出发不在同层内转移的最优答案。
\(g[i]\)表示从\(i\)出发,先转移到同层的某个点的最优答案。
第二问
先把所有可以存在于一个最优方案下的点连边。
因为每条边都至少被覆盖一次,发现这是一个上下界网络流问题。
因为保证有解,直接解决就好了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<vector>
using namespace std;
#define ll long long
#define RG register
#define MAX 50505
#define inf 1e6
inline int read()
{
RG int x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n;
struct Node{int x,y,id;}p[MAX];
bool cmpx(Node a,Node b){if(a.x!=b.x)return a.x<b.x;return a.y<b.y;}
bool cmpy(Node a,Node b){if(a.y!=b.y)return a.y<b.y;return a.x<b.x;}
bool cmpn(Node a,Node b){return a.id<b.id;}
int nt[MAX][5];
map<int,int> M;
int f[MAX],g1[MAX],g2[MAX],g[MAX],zy[MAX],a[MAX];
namespace Task1
{
void Getdirt()
{
sort(&p[1],&p[n+1],cmpx);
for(int i=1;i<n;++i)if(p[i].x==p[i+1].x)nt[p[i].id][2]=p[i+1].id;
for(int i=1;i<=n;++i)if(p[i].x==0){nt[0][2]=p[i].id;break;}//up
M.clear();
for(int i=1;i<=n;++i)//left-up
{
int s=p[i].x+p[i].y;
if(M[s])nt[p[i].id][3]=M[s];
M[s]=p[i].id;
if(s==0)nt[0][3]=p[i].id;
}
M.clear();
for(int i=n;i>=1;--i)//right-up
{
int s=p[i].y-p[i].x;
if(M[s])nt[p[i].id][4]=M[s];
M[s]=p[i].id;
if(s==0)nt[0][4]=p[i].id;
}
}
int st[MAX],top;
void Output(int x,int ans)
{
if(!ans)return;
if(f[x]==ans){printf("%d ",x);Output(zy[x],ans-1);return;}
int pos=a[x];while(pos>1&&p[pos-1].y==p[a[x]].y)--pos;
top=0;while(p[pos].y==p[a[x]].y&&pos<=n)st[++top]=p[pos++].id;
for(int i=1;i<=top;++i)if(st[i]==x){pos=i;break;}
for(int i=1;i<pos;++i)
if(f[st[i]]+top-i==g[x])
{
for(int j=pos;j<=top;++j)printf("%d ",st[j]);
for(int j=pos-1;j>i;--j)printf("%d ",st[j]);
Output(st[i],f[st[i]]);
return;
}
for(int i=top;i>pos;--i)
if(f[st[i]]+i-1==g[x])
{
for(int j=pos;j>=1;--j)printf("%d ",st[j]);
for(int j=pos+1;j<i;++j)printf("%d ",st[j]);
Output(st[i],f[st[i]]);
return;
}
}
void Solve()
{
Getdirt();sort(&p[1],&p[n+1],cmpy);
for(int i=1;i<=n;++i)a[p[i].id]=i;
for(int i=n,pos;i;i=pos)
{
top=0;pos=i;
while(pos&&p[pos].y==p[i].y)st[++top]=p[pos--].id;
reverse(&st[1],&st[top+1]);
for(int j=1;j<=top;++j)
for(int k=2;k<=4;++k)
{
int u=st[j];
if(f[u]<g[nt[u][k]]+1)
f[u]=g[nt[u][k]]+1,zy[u]=nt[u][k];
}
for(int j=1;j<=top;++j)g[st[j]]=f[st[j]];
for(int j=2,nw=1;j<=top;++j)
{
g[st[j]]=max(g[st[j]],f[st[nw]]+top-nw);
if(f[st[nw]]-nw<f[st[j]]-j)nw=j;
}
for(int j=top-1,nw=top;j>=1;--j)
{
g[st[j]]=max(g[st[j]],f[st[nw]]+nw-1);
if(f[st[nw]]+nw<f[st[j]]+j)nw=j;
}
}
for(int k=2;k<=4;++k)
if(f[0]<=g[nt[0][k]])f[0]=g[nt[0][k]],zy[0]=nt[0][k];
printf("%d\n",f[0]);
Output(zy[0],f[0]);puts("");
}
}
namespace Task2
{
struct Line{int v,next,w;}e[MAX<<5];
int h[MAX],cnt=2,M[MAX],SS,TT,S,T;
inline void Add(int u,int v,int w)
{
e[cnt]=(Line){v,h[u],w};h[u]=cnt++;
e[cnt]=(Line){u,h[v],0};h[v]=cnt++;
}
set<pair<int,int> > SSS;
bool vis[MAX];
void Link(int x,int ans,bool fl)
{
if(!ans)return;
if(!fl)if(SSS.count(make_pair(x,ans)))return;
if(!fl)SSS.insert(make_pair(x,ans));
if(f[x]==ans&&!vis[x])
{
vis[x]=true;
for(int k=2;k<=4;++k)
if(f[x]==g[nt[x][k]]+1)
{
Add(x,nt[x][k],inf);
M[x]-=1;M[nt[x][k]]+=1;
Link(nt[x][k],ans-1,1);
}
}
if(!fl)return;
vector<int> st;int top=0;st.clear();st.push_back(0);
int pos=a[x];while(pos>1&&p[pos-1].y==p[a[x]].y)--pos;
top=0;while(p[pos].y==p[a[x]].y&&pos<=n)st.push_back(p[pos++].id),++top;
for(int i=1;i<=top;++i)if(st[i]==x){pos=i;break;}
for(int i=1;i<pos;++i)if(f[st[i]]+top-i==g[x])Link(st[i],f[st[i]],0);
for(int i=top;i>pos;--i)if(f[st[i]]+i-1==g[x])Link(st[i],f[st[i]],0);
}
int level[MAX],cur[MAX];
bool bfs(int S,int T)
{
memset(level,0,sizeof(level));level[S]=1;
queue<int> Q;Q.push(S);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=h[u];i;i=e[i].next)
if(e[i].w&&!level[e[i].v])
level[e[i].v]=level[u]+1,Q.push(e[i].v);
}
return level[T];
}
int dfs(int u,int T,int flow)
{
if(u==T||!flow)return flow;
int ret=0;
for(int &i=cur[u];i;i=e[i].next)
{
int v=e[i].v,d;
if(e[i].w&&level[v]==level[u]+1)
{
d=dfs(v,T,min(flow,e[i].w));
ret+=d;flow-=d;
e[i].w-=d;e[i^1].w+=d;
if(!flow)break;
}
}
return ret;
}
int Dinic(int S,int T)
{
int ret=0;
while(bfs(S,T))
{
memcpy(cur,h,sizeof(cur));
ret+=dfs(S,T,inf);
}
return ret;
}
int ans,rb;
void Solve()
{
S=n+1;T=n+2;SS=n+3;TT=n+4;
f[0]+=1;g[0]=inf;Link(0,f[0],0);
for(int i=0;i<=n;++i)Add(i,T,inf);
for(int i=0;i<=n;++i)Add(S,i,inf);
for(int i=0;i<=n;++i)
if(M[i]>0)Add(SS,i,M[i]);
else Add(i,TT,-M[i]);
Add(T,S,inf);rb=cnt-1;
Dinic(SS,TT);
h[T]=e[h[T]].next;h[S]=e[h[S]].next;
ans-=Dinic(T,S);ans+=e[rb].w;
printf("%d\n",ans);
}
}
int main()
{
n=read();
for(int i=1;i<=n;++i)p[i].x=read(),p[i].y=read(),p[i].id=i;
Task1::Solve();Task2::Solve();
return 0;
}