CF39C MOON CRATERS

MOON CRATERS

题目传送门

http://codeforces.com/problemset/problem/39/C

参考题解!:
这里写图片描述
这里写图片描述

除去这道题本身的思考不谈,考察的是比较基础的区间DP
emmm众所周知,CF的题以思维含量大为显著特征,一些算法光会并没有什么用,要分析具体题目并针对之作出相应应对。

比赛的时候看到这道题时,题目看错了,我以为的是选中的圆必须要满足相切,相离或者相包含其中一种的最优值,然后没想到怎么处理后效性,就打了一个很脑残的DP
没想到还有18分

代码如下:
就用当前的情况去推后面的情况,这样子做的话相离应该是对的,另外没法判断后效性所以就emmmm

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

struct node
{
    long long r,x,s1,s2; 
} a[2010];
bool cmp(node c,node b)
{
    return c.x<b.x;
}
bool cmp2(node c,node b)
{
    return c.s2<b.s2;
}
int n,m,fl[2010],pathl[2010],ansl,ali;
int fq[2010],pathq[2010],ansq,aqi;
int fb[2010],pathb[2010],ansb,abi;

void printb(int x)
{
    if (pathb[x]==0)
      {
        cout<<x<<' ';
        return;
      }
    printb(pathb[x]);
    cout<<x<<' ';
}

void printl(int x)
{
    if (pathl[x]==0)
      {
        cout<<x<<' ';
        return;
      }
    printb(pathl[x]);
    cout<<x<<' ';
}
void printq(int x)
{
    if (pathq[x]==0)
      {
        cout<<x;
        return;
      }
    printb(pathq[x]);
    cout<<x<<' ';
}
int main()
{
    freopen("crater.in","r",stdin);
    freopen("crater.out","w",stdout);
    scanf("%d",&n);
    for (int i=1; i<=n; i++)
      {
        scanf("%d%d",&a[i].x,&a[i].r);
        a[i].s1=a[i].x-a[i].r;
        a[i].s2=a[i].x+a[i].r;
        fq[i]=fl[i]=fb[i]=1;
      }
    sort(a+1,a+n+1,cmp);
    for (int i=1; i<=n; i++)
      for (int j=i+1; j<=n; j++)
          {
            if (a[j].s1>a[i].s2&&fl[i]+1>fl[j])   
              {
                fl[j]=fl[i]+1;
                pathl[j]=i;
              }//求相离 
            if ((a[j].s1==a[i].s1||a[i].s2==a[j].s1||a[i].s2==a[j].s2)&&fq[j]<fq[i]+1)
              {
                fq[j]=fq[i]+1;
                pathq[j]=i;
              } //相切 
          }
    for (int i=1; i<=n; i++)
      if (ansl<fl[i]) ansl=fl[i],ali=i;
    for (int i=1; i<=n; i++)
      if (ansq<fq[i]) ansq=fq[i],aqi=i;
    sort(a+1,a+n+1,cmp2);
    for (int i=1; i<=n; i++)
      for (int j=i+1; j<=n; j++)
        {
            if (a[i].s1>=a[j].s1&&a[i].s2<=a[j].s2&&fb[j]<fb[i]+1)
              {
                fb[j]=fb[i]+1;
                pathb[j]=i;
              }
        }//包含 
    for (int i=1; i<=n; i++)
      if (ansb<fb[i]) ansb=fb[i],abi=i;
    if (ansb>=ansl&&ansb>=ansq)
      {
        cout<<ansb<<endl;
        printb(abi);
        return 0;
      }
    if (ansl>=ansb&&ansl>=ansq)
      {
        cout<<ansl<<endl;
        printl(ali);
        return 0;
      }
    if (ansq>=ansl&&ansq>=ansb)
      {
        cout<<ansq<<endl;
        printq(aqi);
        return 0;
      }
}

然后ak做法
考虑到后效性的话,
具体做法:
我们把所有圆的端点都记下来,排序去重(去重的原因,比如说,1 1 1 1 2,不去重的话,1离散化成了 1 2 3 4 ,2就变成了 5,这个结合后面),后离散化(比如说, 10 15 20 25 30,离散化成1 2 3 4 5,这样可以避免DP时那些不需要的坐标的重复计算),然后,再以每个圆的起始端点和结束端点(l,r)作为dp的区间,做区间dp,
f[r]=max(f[r-1[,f[l]+w[t]),w数组就是这个区间内能够相离或者外切的最大圆数,
w[i]=f[maxr]+1.
注意点是端点必须要分割在每一个圆的起始和结尾。

最后再给出神仙代码:


 #include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define rep(i,n) for (int i=0;i<n;i++)
const int N=4010;
int n,c,r,x[N],y[N],d[N],g[N][N],f[N][N],s[N][N],t;
vector<int> b[N];
int dp(int l,int r) {
    if (l>r) return 0;
    if (f[l][r]!=-1) return f[l][r];
    f[l][r]=dp(l+1,r);
    for (int k=0; k<b[l].size(); k++) 
      if (y[b[l][k]]<r&&dp(l,y[b[l][k]])+dp(y[b[l][k]],r)>f[l][r])
        f[l][r]=f[l][y[b[l][k]]]+f[y[b[l][k]]][r],s[l][r]=y[b[l][k]];
    f[l][r]+=!!g[l][r];
    return f[l][r];
}
void dfs(int l,int r) {
    if (l>r) return;
    if (s[l][r]) dfs(l,s[l][r]),dfs(s[l][r],r);else dfs(l+1,r);
    if (g[l][r]) printf("%d ",g[l][r]);
}
int main() {
    freopen("crater.in","r",stdin);
    freopen("crater.out","w",stdout);
    scanf("%d",&n);
    rep(i,n) scanf("%d%d",&c,&r),x[i]=c-r,y[i]=c+r,d[i]=x[i],d[i+n]=y[i];
    sort(d,d+n+n);
    t=unique(d,d+n+n)-d;//排序去重
    rep(i,n) 
      {
        x[i]=upper_bound(d,d+t,x[i])-d;y[i]=upper_bound(d,d+t,y[i])-d;
        b[x[i]].push_back(i);g[x[i]][y[i]]=i+1;//离散化
      }
    memset(f,-1,sizeof(f));
    printf("%d\n",dp(1,t));
    dfs(1,t);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值