cogs 2287. [HZOI 2015]疯狂的机器人 (NTT优化DP)

题目描述

  1. [HZOI 2015]疯狂的机器人
    ★★★ 输入文件:crazy_robot.in 输出文件:crazy_robot.out 简单对比
    时间限制:1 s 内存限制:512 MB
    【题目描述】

现在在二维平面内原点上有一只机器人
他每次操作可以选择向右走,向左走,向下走,向上走和不走(每次如果走只能走一格)
但是由于本蒟蒻施展的大魔法,机器人不能走到横坐标是负数或者纵坐标是负数的点上
否则他就会big bang
给定操作次数n,求有多少种不同的操作序列使得机器人在操作后会回到原点
输出答案模998244353后的结果
注意如果两个操作序列存在某一时刻操作不同,则我们认为这两个操作序列不同
【输入格式】

输入n,表示操作次数
n<=100000
【输出格式】

按要求输出答案
【样例输入】

3
【样例输出】

7
【提示】

样例解释:
机器人有7种操作序列
1、不走 不走 不走
2、不走 向右 向左
3、向右 不走 向左
4、向右 向左 不走
5、不走 向上 向下
6、向上 不走 向下
7、向上 向下 不走

题解

因为最终要回到原点,所以向上和向下的步数相等,向左和向右的步数相等。
需要保证在行走的过程中,不能走到负数区域,那么就是在单独看向上和向下两种走法时,任意时刻向上走的步数大于等于向下走的步数,向左和向右也是同理。
这是一个经典的卡特兰数问题,如果向上和向下一共走了 i 步,那么单考虑这两种走法,方案数g[i] Ci/2iCi/2+1i 。那么向左向右也是同理。
设一共走了i步那么不考虑不走的情况,总方案数为
f[j]=ji=0g[i]g[ji]Cij
=ji=0g[i]i!g[ji](ji)!j!
然后发现是卷积的形式,由于998244353是费马质数,所以我可以用NTT来加速。
最后我们只需要考虑上原地不动的就可以
ans=i=0nf[i]Cin

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 500030
#define LL long long
#define p 998244353
using namespace std;
int n,n1,m,L;
LL f[N],g[N],jc[N],R[N];
LL quickpow(LL num,LL x)
{
    LL base=num%p; LL ans=1;
    while (x) {
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
LL calc(LL n,LL m)
{
    if (n<m) return 0;
    return jc[n]*quickpow(jc[n-m]*jc[m]%p,p-2)%p;
}
void NTT(LL x1[N],int n,int opt)
{
    int j;
    for (int i=0;i<n;i++) 
     if (i<R[i]) swap(x1[i],x1[R[i]]);
    for (int i=1;i<n;i<<=1) {
        LL wn=quickpow(3,(p-1)/(i<<1));
        for (int p1=i<<1,j=0;j<n;j+=p1) {
            LL w=1;
            for (int k=0;k<i;k++,w=(w*wn)%p) {
                LL x=x1[j+k],y=(w*x1[j+k+i])%p;
                x1[j+k]=(x+y)%p; x1[j+k+i]=(x-y+p)%p;
            }
        }
    }
    if (opt==-1) reverse(x1+1,x1+n);
}
int main()
{
    freopen("crazy_robot.in","r",stdin);
    freopen("crazy_robot.out","w",stdout);
    scanf("%d",&n);
    jc[0]=1;
    for (int i=1;i<=n;i++) jc[i]=(jc[i-1]*(LL)i)%p;
    for (int i=1;i<=n;i++){
     if (!(i&1)) f[i]=calc(i,i/2)-calc(i,i/2+1);
     f[i]=(f[i]+p)%p;
    }
    f[0]=1;
    for (int i=0;i<=n;i++) f[i]=f[i]*quickpow(jc[i],p-2)%p;
    m=n*2; n1=0;
    for (n1=1;n1<=m;n1<<=1) L++;
    for (int i=0;i<=n1;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
    NTT(f,n1,1); //cout<<n1<<endl;
    for (int i=0;i<=n1;i++) g[i]=f[i];
    for (int i=0;i<=n1;i++) f[i]=(f[i]*g[i])%p;
    NTT(f,n1,-1);
    LL inv=quickpow(n1,p-2); 
    for (int i=0;i<=n1;i++) f[i]=f[i]*inv%p;
    for (int i=0;i<=n1;i++) f[i]=f[i]*jc[i]%p;
    LL ans=0;
    for (int i=0;i<=n;i++)
     ans=(ans+f[i]*calc(n,i)%p)%p;
    printf("%I64d\n",ans);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是将代码修改为cot平滑的方法: 1. 首先,需要使用边界角的cot权重计算每个顶点的权重。 2. 然后,使用cot权重对每个顶点的邻域点进行加权计算,得到平滑后的坐标。 3. 最后,根据平滑后的坐标更新每个顶点的位置。 修改后的代码如下: float smooth() { float err = -1; cogs.clear(); v_end = mesh.vertices_end(); //cot平滑 for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it) { cog[0] = cog[1] = cog[2] = weight_sum = 0.0; for (vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) { double cot_weight = 0.0; MyMesh::HalfedgeHandle heh = mesh.find_halfedge(*v_it, *vv_it); if (!mesh.is_boundary(heh)) { MyMesh::HalfedgeHandle prev_heh = mesh.prev_halfedge_handle(heh); MyMesh::HalfedgeHandle next_heh = mesh.next_halfedge_handle(heh); MyMesh::VertexHandle prev_vh = mesh.to_vertex_handle(prev_heh); MyMesh::VertexHandle next_vh = mesh.to_vertex_handle(next_heh); MyMesh::Point prev_p = mesh.point(prev_vh); MyMesh::Point curr_p = mesh.point(*v_it); MyMesh::Point next_p = mesh.point(next_vh); double cot_alpha = cot(prev_p - curr_p, next_p - curr_p); double cot_beta = cot(curr_p - prev_p, next_p - prev_p); cot_weight = cot_alpha + cot_beta; } cog += cot_weight * mesh.point(*vv_it); weight_sum += cot_weight; } cogs.push_back(cog / weight_sum); } for (v_it = mesh.vertices_begin(), cog_it = cogs.begin(); v_it != v_end; ++v_it, ++cog_it) { if (!mesh.is_boundary(*v_it)) { MyMesh::Point p = mesh.point(*v_it); err = max(err, (p - *cog_it).norm()); mesh.set_point(*v_it, *cog_it); } } return err; } 其中cot函数的定义如下: double cot(MyMesh::Point a, MyMesh::Point b) { return dot(a, b) / cross(a, b).norm(); } 注意,这里使用的是边界角的cot权重,因此在计算cot权重时需要判断当前边是否为边界。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值