【NOIP模拟】20140812 题解 & 总结

  今天继续刷了一套题……解法都不难但比较难想到,但据moreD神犇说这其实很容易想到……应该是题量不足的缘故?OrzOrzOrz~~

 

  T1:过河

  原题:{由于内容限制不发图……}

  题目大意:{由于难以概述大意因此不描述}

  (群众:这不是坑爹吗?连题目都看不到让我们看题解有意思???)

  (Michael Long:嘿嘿……那还是发原题吧,某人不要介意啊……)

  有(N+1)个平行于x轴的河岸排成一排(N<=50000),每两个河岸之间夹着一条河,所以一共有N条河。第i 条河的宽度为wi,xxx在第i 条河中行进的速度为vi。河岸的宽度忽略不计。令X=sigma(wi)。
  规定:
  1、xxx从(0,0)出发,终点是(X,Y)。Y是一个给定的整数。
  2、xxx在渡河时,必须从一个整点驶向另一个整点,花费的时间为这两个点的欧几里得距离除以速度。
  3、xxx可以在河岸上行走,但也是必须从一个整点走向另一个整点,速度为给定的u。
  求花费时间的最小值。范围:u、wi、vi<=10^5。

  比较暴力的方法是设DP方程F[i,j]表示走到第i行第j列所需的最小时间,转移嘛……时间复杂度为暴力的O(NY^2),还可以骗个30分……

  正解的思路很巧妙:

  如果现在让你找一条路线(不一定最优),你一定会选择先向下走过N行后向右走到第Y列,如图。总时间可以马上计算出来。

 

  这时候如果将路线分段,每一段为路线中其中一行到下一行的路线,可以考虑让其中一段路稍稍改变,让总时间减少。为什么要这样考虑?因为改变这一段路线其实并不改变其他N-1段路线的长度以及时间(显然)。怎么改变?当然是将下一行上的端点向右移动1个单位(为什么不能向左移动?显然这只会增加时间):

 

  这样改变对总时间的影响是多少呢?因为上图中这条绿色的倾斜的路线实际上替代了2段粉色的路线,因此对总时间的增加或减少是可以计算出来的(怎么计算??可以用走绿色斜线的时间减去走原路线即2段粉色路线的时间)。如果总时间减少了那么这次路线改变就是成功的。对于N段路线我们都可以考虑这样的改变,最多可以改变Y次(为什么?请仔细思考)。

  那么一个基于贪心思想的解法就出来了:每一次在N段路线中选择1条改变路线后对总时间影响最大的路线进行改变,直至改变了Y次或者没有能减少时间的改变为止。由于实际上要在N个数中找一个最小的数并且进行修改,因此可以用一个堆维护这N个数,在O(logN)的时间内就可以完成一次修改,总的时间复杂度为O(YlogN)

  PS:由于C++的队列库中有优先队列可以用,为省时间- -C++实现这道题……

#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;

const int maxn=50010;

struct node{
	double x;
	int a,b,c;
};

int n,Y,u,w[maxn],v[maxn];

priority_queue
       
       
         q; bool operator <(node a,node b){ return a.x>b.x; } double dis(int x,int y){ return sqrt(x*x+y*y); } int main(){ freopen("river.in","r",stdin); freopen("river.out","w",stdout); cin>>n>>Y>>u; fo(i,1,n) cin>>w[i]; fo(i,1,n) cin>>v[i]; double ans=0; fo(i,1,n){ node t; t.x=(dis(w[i],1)-w[i])/v[i]-(double)(1)/u; t.a=w[i]; t.b=1; t.c=v[i]; q.push(t); ans=ans+(double)(w[i])/v[i]; } ans=ans+(double)(Y)/u; fo(i,1,Y){ node now=q.top(); q.pop(); if (now.x>=0) break; ans=ans+now.x; now.x=(dis(now.a,now.b+1)-dis(now.a,now.b))/now.c-(double)(1)/u; ++now.b; q.push(now); } printf("%.4f\n",ans); return 0; } 
       
      
      
     
     
    
    
   
   

  T2:逃离迷宫

  原题:{由于内容限制不发图……}

  题目大意:给出一个N×M的迷宫(N×M<=1000),每个格子上下左右都连通,每个格子都有一个高度,从一个格子行走到另一个格子需要消耗一定的体力(高度差的平方),有K个格子中有回复一定体力的药水(K<=15),每瓶药水只能喝一次,求从起始格子走到终点格子所需要消耗的最小体力。

  最喜欢走迷宫类的题目了- -,因为这一般都是比较简单的BFS……但这道题中加入了可以减少移动代价的“药水”,单纯的BFS也不能保证队列的单调性。因此除了改进BFS外,还可以将迷宫转化为无向图,使用最短路算法解决。

  但是药水的使用次数有限,如果直接在由迷宫转化成的图上跑最短路,可能会出现负环的情况(因为无法判断某瓶药水是否已经用过)。而实际上图上最多只有17个特殊的点(起点、终点、药水点),因此我们以这17个点为起点做17次最短路,得出这17个点两两之间的最短距离(不考虑药水);然后再设状态压缩DP方程F[i,S]表示从起始点到第i个特殊点、药水的使用状态为S的最小代价,转移就很容易了,而且完美解决了药水的使用次数限制的问题。时间复杂度O(NM*2^K)

 

const
    fx:array[1..4,1..2] of longint=((1,0),(-1,0),(0,1),(0,-1));
var
    a:array[1..1000,1..1000] of longint;
    b:array[0..16,0..16] of longint;
    c:array[0..16,1..2] of longint;
    s:array[0..15] of longint;
    q:array[1..10000,1..2] of longint;
    f:array[0..15,1..65535] of longint;
    n,m,i,j,k,pc,ans:longint;
function min(x,y:longint):longint; begin if x
  
  
   
   0) and (x<=n) and (y>0) and (y<=m) then begin
                    z:=conv(x,y);
                    if d[z]>d[t]+sqr(a[x,y]-a[q[i,1],q[i,2]]) then begin
                        d[z]:=d[t]+sqr(a[x,y]-a[q[i,1],q[i,2]]);
                        if p[z] then begin
                            p[z]:=false;
                            inc(j);
                            q[j,1]:=x;
                            q[j,2]:=y;
                        end;
                    end;
                end;
            end;
            p[t]:=true;
        end;
        for i:=0 to pc+1 do b[v,i]:=d[conv(c[i,1],c[i,2])];
    end;
begin
    assign(input,'escape.in');reset(input);
    assign(output,'escape.out');rewrite(output);
    readln(n,m,pc,c[0,1],c[0,2],c[pc+1,1],c[pc+1,2]);
    for i:=1 to n do begin
        for j:=1 to m do read(a[i,j]);
        readln;
    end;
    for i:=1 to pc do readln(c[i,1],c[i,2],s[i]);
    for i:=0 to pc do spfa(i);
    fillchar(f,sizeof(f),60);
    f[0,1]:=0;
    ans:=maxlongint;
    for j:=1 to 1 shl (pc+1)-1 do
        for i:=0 to pc do if (1 shl i) and j>0 then begin
            for k:=0 to pc do if (1 shl k) and j=0 then
                f[k,j+1 shl k]:=min(f[k,j+1 shl k],f[i,j]+b[i,k]-s[k]);
            ans:=min(ans,f[i,j]+b[i,pc+1]);
        end;
    writeln(ans);
    close(input);close(output);
end.

  
  


  T3:幸运数

  原题:{由于内容限制不发图……}

  题目大意:求1..N中幸运数的个数(N<=10^9),其中一个数为幸运数的要求是这个数的质因子都不超过M且每种质因子的个数为奇数(M<=10^6)。

  看到对于幸运数的定义,很容易想到求出2..M中的所有质数并通过暴力枚举每种质因数的个数枚举幸运数。时间复杂度O(Ans),但暴力求解得知极限情况下Ans约为4亿,加上常数时间……实际上这个做法只能得到40分。

  考虑优化这个暴力算法,有一个不为人注意的剪枝(被moreD神犇发现了)——如果设当前枚举得到的幸运数为P,当前正在枚举的质因数为X,则当P*X^2>N时,X及比X大的质因子对于当前的P最多只能取1。因此可以使用二分确定一个范围,这个范围中的质因数乘以P都满足小于等于N(且一定都是幸运数),那么这些新的幸运数的个数就可以用log级别的时间复杂度得知(二分的时间复杂度),加上这个优化后就可以过了- -。总的时间复杂度难以估计……。

var
    a:array[0..500000] of longint;
    f:array[2..1000000] of boolean;
    n,m,i,j,ans:longint;
procedure dg(v:longint;p:int64);
    var
        l,r,md:longint;
        q:boolean;
        t:int64;
    begin
        if (v>a[0]) or (p*a[v]>n) then exit;
        t:=p*a[v]*a[v];
        if t>n then begin
            l:=v;
            r:=a[0];
            while l<=r do begin
                md:=(l+r) shr 1;
                if p*a[md]>n then r:=md-1 else l:=md+1;
            end;
            inc(ans,r-v+1);
            exit;
        end;
        dg(v+1,p);
        p:=p*a[v];
        while p<=n do begin
            inc(ans);
            dg(v+1,p);
            p:=p*a[v];
            if p>n then break;
            p:=p*a[v];
        end;
    end;
begin
    assign(input,'lucky.in');reset(input);
    assign(output,'lucky.out');rewrite(output);
    readln(n,m);
    for i:=2 to m do begin
        if not f[i] then begin inc(a[0]); a[a[0]]:=i; end;
        for j:=1 to a[0] do begin
            if i*a[j]>m then break;
            f[i*a[j]]:=true;
            if i mod a[j]=0 then break;
        end;
    end;
    ans:=1;
    dg(1,1);
    writeln(ans);
    close(input);close(output);
end.

  总结:这套题我没有一道题ACT1得到30分,T2T3各得40分,共110分。不得不说这些题目的正解都相当巧妙,看似简单但不是那么容易就可以看出来的。就和上面所说一样,除了题量可能不足外,没有完全消化吸收以前做过的题也是一个可能的原因。看来除了埋头改题,还要注意看这些题目和针对这些题目的解法的特点和套路,从而下次做相似类别的题目时能够靠自己想出相似的解法。这点不是那么容易就可以做到的,但是现在积累也为时不晚!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值