[清橙A1210]光棱坦克-动态规划

光棱坦克

试题来源

  2010中国国家集训队命题答辩

问题描述

  
  一个平面直角坐标系上,有N个点,标号为1到N,其中第i个点的坐标为(x[i], y[i])。
  求满足以下两个条件的点列{p[i]}的数目(假设{p[i]}的长度为M):
  1) 对任意1 <= i < j <= M,必有y[p[i]] > y[p[j]];
  2) 对任意3 <= i <= M,必有x[p[i-1]] < x[p[i]] < x[p[i-2]]或者x[p[i-2]] < x[p[i]] < x[p[i-1]]。
  求满足条件的非空序列{p[i]}的数目,结果对一个整数Q取模。
  

输入格式

  第1行是两个由空格隔开的整数:N和Q。
  第2行到第N+1行,每行有两个整数。其中的第i行的两个整数分别是x[i]和y[i]。
  

输出格式

  输出文件只有一行,包含一个整数,表示序列{p[i]}的数目对Q取模的结果。

样例输入

4 100
2 2
3 1
1 4
4 3

样例输出

14

样例说明

  一共4个点,位置如下:
1
  如果M=4, 那么只有1种序列符合要求,如下图所示:
2
  如果M = 3,那么有3种序列符合要求,如下图:
3
  如果M = 2,那么有6种序列符合要求,如下图:
4

  如果M = 1,也就是点列只包含一个点的情况。那么有4种序列。明显都符合要求。
  所以一共就有1 + 3 + 6 + 4一共14种序列符合要求。

数据规模和约定

  对于25%的数据,N <= 50;对于40%的数据,N <= 700;对于60%的数据,N <= 2000;对于70%的数据,N <= 4000;对于100%的数据,1 <= N <= 7000。
  对于100%的数据,有1 <= Q <= 1000000000。
  对于50%的数据,保证对任何的i,x[i]和y[i]是1到N之间的整数;对于100%的数据,保证对任何的i,x[i]和y[i]都是1到2000000000之间的整数。
  对于100%的数据,保证有当i != j时,有x[i] != x[j]且y[i] != y[j]。
  


去年三月份和某dalao想了一下午做出来的题……
这个dalao(就是友链里的第二个)拿去晚间划水(晚间水题选讲)讲掉了……
于是决定写一篇博客…..
毕竟是道很好的题……


思路:
观察样例,考虑将最终的方案按顺序连边,你会发现这极其像一个简笔画的龙卷风……

如果思考如何按 y 的顺序进行dp,你会发现貌似想不到什么东西,仅有的几个想法也极其难写。
(有dalao想到了方法欢迎打脸)

那么考虑从不那么自然的角度——按 x 的顺序进行dp。
考虑令 f[i][0/1] 代表以 i 为起点,且下一个节点在i的左边/右边的方案数。

那么可以得出一个很奇怪的转移:
从右到左枚举所有 j<i ,若 y[j]>y[i]
那么 i 转移到j f[j][1]+=f[i][0]
否则从 j 转移到i f[i][0]+=f[j][1]

最难理解的其实是这个从 i 转移到j看着有些像后效性…..

为什么这样转移?
如果在处理 i 点时,发现一个j满足 y[j]>y[i]
注意,此时的 f[i][0] 已经保存了所有在 (j,i) 区间中的方案,从而保证了”长得像龙卷风”这一性质。
那么这就相当于把 i 接到j的右边,继续构成咱们的龙卷风图案。

而这个被更新过的 f[j][1] 又可以用于更新其他满足 y[j]<y[i] f[i][0] ,相当于把 j 接到了这样的i的左边。
并且可以发现,此时的 i x坐标一定大于原先用于更新 f[j][1] i x坐标,同样保证了”长得像龙卷风”这一性质。

于是这个dp方法便十分有理有据辣~
这个又短又快的 O(n2) 算法顶着清橙的老爷机跑出了 0.2s 的好成绩!

写下以下代码时作者还处于习惯性打括号时期~

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct po{
    int x,y;
}p[7050];
int dp[7050][2];
int modd,n; 
bool cmp(po a,po b){
    return a.x<b.x;
}
int main(){
    scanf("%d%d",&n,&modd);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&p[i].x,&p[i].y);
    }

    sort(p+1,p+n+1,cmp);

    for(int i=1;i<=n;i++)
    {
        dp[i][0]=dp[i][1]=1;

        for(int j=i-1;j>=1;j--)
        {
            if(p[j].y>p[i].y)
            {
                dp[j][1]+=dp[i][0];

                if(dp[j][1]>modd)
                    dp[j][1]-=modd;
            }
            else
            {
                dp[i][0]+=dp[j][1];

                if(dp[i][0]>modd)
                    dp[i][0]-=modd;
            }
        }
    }

    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=dp[i][1];
        if(ans>modd)
            ans-=modd;
        ans+=dp[i][0];
        if(ans>modd)
            ans-=modd;
    }

    ans-=n;
    if(ans<0)
        ans+=modd;
    printf("%d\n",ans);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值