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