【五校联考5day1】登山

题意

给你一个n*n的地图,让你从(0,0)走到(n,n),且到了对角线时只能走(x+1,x),而且不能走到障碍物里(有c个),问你走的方案数。(mod 109+7

数据范围

对于30%的数据,保证N<=5000
对于另外20%的数据,保证C=0
对于另外20%的数据,保证C=1
对于100%的数据,保证N<=100000,C<=1000
保证对于(0,0),(N,N)不存在障碍点。

分析

对角线

这道题其实很像【GDOI2015】Hearth Stone。。。
我们可以分类讨论:
我们可以讨论一下我们违法的方案,且这个违法的方案是走对角线的。
这是一张神奇的图。(转自Philipsweng)

引用块内容

粉色的那条直线是不能走的,那么我们可以把触碰到粉线的方案对称到另一边,所以我们可以把走到终点的方案对称到对应点,求到对应点的方案数即可。

经过其它的障碍点

我们可以从c入手,我们发现c很小,所以是否是根据c去做呢?
事实就是这样的。

类似容斥原理(补集的思想)

我们可以用类似容斥原理的思想去想。
走到第i个点的不走其他的障碍点的方案数=走到第i个障碍点可以从无限制走到第i个障碍点的方案数-走时可以走一些障碍点的方案数。
但若是我们不能直接去计算,直接转移,因为这样是毫无顺序的,也就是说我们会算到一些重复的方案。
所以:
我们设一个f[i],表示我们从(0,0)走到第i个障碍点的除当前点的方案。g[i]表示我们从(0,0)走到第i个障碍点可以经过一些障碍点。那么转移为:
g[i]=j=1i1f[j]ways(pj,pi) (pi表示第i个障碍点的坐标, ways(x,y) ,表示我们从第x个关键点走到第y个关键点的不经过对角线的方案数)
然而我们可以很轻松的算出 ways(x1,y1,x2,y2) .
我们设 s1=(x2x1),s2=(y2y1);ways(x1,y1,x2,y2)=C(s2s2+s1)
至于为什么g可以这样求呢,因为:
我们设一条路径是这样的 p1,p2,p3,p4,..pi
那么我们只用限制于是否走第j个点,那么后面的点是可以随便走的,这样我们便可以保证我们的统计是对的了(每一次统计走的都不是同一个点,也就是说我们强制它先只走一个点,所以这是不会重复的,是有序的)。
而最后只要把终点加进障碍点即可。
答案便是 ways(0,0,n,n)g[c+1]

最终的复杂度为 O(c2) ,很好的解决了这个问题。

代码:
当然我打这道题用了大约一个下午原因是因为我脑残打多了一个符号。。

const mo=1000000007;
var
     n,m,s,t,i,j:longint;w:int64;
     x:array[0..100000,1..2] of longint;
     fac,pop:array[0..300005] of int64;
     f,g:array[1..100000] of int64;
function get(y,x:longint):int64;
begin
    if x=1 then exit(y)
    else begin
       get:=get(y,x div 2);
       get:=(get*get)mod mo;
       if x mod 2<>0 then get:=(get*y)mod mo;
    end;
end;
procedure qsort(l,r:longint);
var i,j,mid,mie:longint;
begin
    i:=l;j:=r;mid:=x[(l+r)shr 1,1];mie:=x[(l+r)shr 1,2];
    repeat
        while (x[i,1]<mid)or(x[i,1]=mid)and(x[i,2]<mie) do inc(i);
        while (x[j,1]>mid)or(x[j,1]=mid)and(x[j,2]>mie) do dec(j);
        if i<=j then begin x[0]:=x[i];x[i]:=x[j];x[j]:=x[0];inc(i);dec(j);end;
    until i>j;
    if l<j then qsort(l,j);
    if i<r then qsort(i,r);
end;
function ways(l,r,s,t:longint):int64; var s1,t1,su1,pe:int64;
begin
    s1:=t-1;t1:=s+1;
    if (l>s1)or(r>t1) then su1:=0 else
    if (s1=l)or(t1=r) then su1:=1 else begin
       s1:=s1-l;t1:=t1-r;su1:=((fac[s1+t1]*((pop[s1]*pop[t1])mod mo))mod mo);
    end;
    if (l>s)or(r>t) then pe:=0 else
    if (s=l)or(t=r) then pe:=1 else begin
    s:=s-l;t:=t-r;pe:=(fac[s+t]*((pop[s]*pop[t])mod mo))mod mo;end;
    exit((pe-su1+mo)mod mo);
end;
begin
    readln(n,m);
    fac[0]:=1;
    for i:=1 to 3*n do begin fac[i]:=(fac[i-1]*i)mod mo;pop[i]:=get(fac[i],mo-2);end;
    for i:=1 to m do read(x[i,1],x[i,2]);
    if m=0 then begin
       writeln(ways(0,0,n,n)mod mo);
    end else begin inc(m);x[m,1]:=n;x[m,2]:=n;
        qsort(1,m);
        f[1]:=ways(0,0,x[1,1],x[1,2]);
        for i:=2 to m do begin
            for j:=1 to i-1 do if x[j,2]<=x[i,2] then begin
                w:=ways(x[j,1],x[j,2],x[i,1],x[i,2]);
                g[i]:=(g[i]+(f[j]*w)mod mo)mod mo;
            end;
            w:=ways(0,0,x[i,1],x[i,2]);
            f[i]:=(w-g[i]+mo)mod mo;
        end;
        writeln(f[m]);
    end;
end.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值