CF39C Solution
题目翻译
给出直线上的 \(n\) 个圆,圆心坐标为 \(c_i\)、半径为 \(r_i\)。如果若干个圆不相交(相离、相切或包含),则称这些圆符合理论。请你求出符合理论圆的最大集合。
题解
记录路径鲨我>︿<
状态:\(dp[i][j]\)表示离散化后(最大\(2n\))的下标\([i,j]\)区间内符合理论的最大圆数。
转移方程:设\(b[i][]\)为左端点为\(i\)的圆编号,\(cir[i][j]\)表示是否(\(1/0\))有圆左右端点分别为\(i,j\)。
\[dp[i][j]=min(dp[i+1][j],dp[l][b[i][k]]+dp[b[i][k]][r])+cir[i][j]\quad (1\le i<j\le n)
\]
没有圆以\(i\)为左端点则取\(dp[i+1][j]\),否则枚举以\(i\)为左端点的圆,以其右端点为断点。如果直接枚举断点时间复杂度为\(O(n^3)\),且易得只有以圆的端点为断点时可以对答案产生影响。
目标状态:\(dp[1][cnt]\)(\(cnt\)为离散化后的下标个数)。
记录路径:\(pre[i][j]\)表示\(dp[i][j]\)选取的断点,若从\(dp[i+1][j]\)转移而来则为\(-1\)。递归,若存在圆以\(i,j\)为左右端点则输出该圆编号。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=4010;
int c[N],r[N],qwq[N],a[N],b[N],dp[N][N],pre[N][N],cir[N][N],ans[N],tot,cnt;
vector<int> s[N];
inline int read()
{
int s=0,w=1; char ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-') w=-1; ch=getchar();}
while(ch>='0' && ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
void path(int x,int y)
{
if(cir[x][y]) ans[++tot]=cir[x][y];
if(!pre[x][y]) return;
if(pre[x][y]==-1) path(x+1,y);
else path(x,pre[x][y]),path(pre[x][y],y);
}
int main()
{
int n,x=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&c[i],&r[i]);
qwq[++cnt]=c[i]-r[i],qwq[++cnt]=c[i]+r[i];
}
sort(qwq+1,qwq+cnt+1); cnt=unique(qwq+1,qwq+cnt+1)-qwq-1;
for(int i=1;i<=n;i++)
{
a[i]=lower_bound(qwq+1,qwq+cnt+1,c[i]-r[i])-qwq;
b[i]=lower_bound(qwq+1,qwq+cnt+1,c[i]+r[i])-qwq;
cir[a[i]][b[i]]=i,s[a[i]].push_back(i);
}
for(int i=1;i<=cnt;i++)
{
for(int l=1;l+i<=cnt;l++)
{
int r=l+i,sz=s[l].size();
dp[l][r]=dp[l+1][r],pre[l][r]=-1;
for(int k=0;k<sz;k++)
{
if(b[s[l][k]]>=r) continue;
if(dp[l][b[s[l][k]]]+dp[b[s[l][k]]][r]>dp[l][r])
dp[l][r]=dp[l][b[s[l][k]]]+dp[b[s[l][k]]][r],pre[l][r]=b[s[l][k]];
}
dp[l][r]+=(cir[l][r]>0);
}
}
path(1,cnt);
printf("%d\n",tot);
for(int i=1;i<=tot;i++) printf("%d ",ans[i]);
return 0;
}