NOIP2013 提高组复赛


day1

day\;1

1002. 火柴排队

  • 贪心+数据结构/归并排序

这个“相邻交换”让我联想到了NOIP2012_day1_task2_game那题的恶心做法,于是就专注推导相邻两个元素交换对解的影响。然后根据以前经验知道一定有一个序列可以完全不动,而另一个序列只需要以第一列作为标准移动(所以样例解释反而给的误导很大)。于是很快就确信正解了。

现在想起来还有一点小激动,自己居然能找出规律来。

首先,一定可以只对某一组的元素进行交换,能以给定的最少步数,得到另外一组的结果。显然当前序列还缺少k步就和另外一个序列相同时,另外一个序列一定也只需要k步就可以和当前序列相同。

其次,由于只能相邻交换,对于每一次的操作,它对于解的影响一定只与这一对元素有关。于是我们对相邻元素的数量关系进行推导:

假设有两组元素 ai,ai+1 a_i,a_{i+1}和 bi,bi+1 b_i,b_{i+1},如果有

(ai−bi)2+(ai+1−bi+1)2<(ai−bi+1)2+(ai+1−bi)2

(a_i-b_i)^2+(a_{i+1}-b_{i+1})^2\lt (a_i-b_{i+1})^2+(a_{i+1}-b_i)^2

则说明有 {bi,bi+1} \{b_i,b_{i+1}\}的顺序比 {bi+1,bi} \{b_{i+1},b_i\}更优。对上述式子推导有:

2aibi+1+2ai+1bi<2aibi+2ai+1bi+1

2a_ib_{i+1}+2a_{i+1}b_i\lt 2a_ib_i+2a_{i+1}b_{i+1}

ai(bi+1−bi)+ai+1(bi−bi+1)<0

a_i(b_{i+1}-b_i)+a_{i+1}(b_i-b_{i+1})

→(ai−ai+1)(bi−bi+1)>0

\to(a_i-a_{i+1})(b_i-b_{i+1})>0

显然上式满足的唯一情况就是 ai>ai+1,bi>bi+1 a_i>a_{i+1},b_i>b_{i+1}或 ai<ai+1,bi<bi+1 a_i。也就是说,要求两个序列的元素大小单调且对应。于是我们只要依次配对 a a序列最大,次大…的位置和bb序列最大,次大…的位置即可。

我们对于 a a序列中元素,在bb序列中找到它应当对应的元素在 b b序列的位置。也就是对于aa序列中1,2,3,4…的顺序,到 b b序列中就成了1~n的全排列。于是问题就转化成要将bb序列的这个排列有序的最少相邻交换次数。

于是我们想到了冒泡排序和逆序对,随便求求逆序对的个数就可以了。采用归并排序完成的方法和数据结构统计的方法都是可以的。

Code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define M 100005
#define P 99999997
using namespace std;
template <class temp>
inline void Rd(temp &res){res=0;char c;while(c=getchar(),c<48);do res=(res<<3)+(res<<1)+(c^48);while(c=getchar(),c>47);
}
int n,a[M],b[M],p[M],q[M];
long long cnt=0;
bool cmp(int a1,int a2){return b[a1]<b[a2];}
bool _cmp(int p1,int p2){return a[p1]<a[p2];}
void Merge(int L,int R){if(L==R)return;int mid=L+R>>1;Merge(L,mid),Merge(mid+1,R);int low=L,high=mid+1,tot=L;while(low<=mid&&high<=R)if(b[low]<b[high])p[tot++]=b[low++];else{cnt+=mid-low+1;p[tot++]=b[high++];}while(low<=mid)p[tot++]=b[low++];while(high<=R)p[tot++]=b[high++];for(int i=L;i<=R;i++)b[i]=p[i];
}
int main(){Rd(n);for(int i=1;i<=n;i++)Rd(a[i]),q[i]=i;for(int i=1;i<=n;i++)Rd(b[i]),p[i]=i;sort(q+1,q+n+1,_cmp);sort(p+1,p+n+1,cmp);for(int i=1;i<=n;i++)b[p[i]]=q[i];Merge(1,n);cnt%=P;printf("%d\n",(int)cnt);
}

1003. 火车运输

现在遇到这种走路径限值的题目就非常害怕。因为标程几乎与Dijkstra那种图论算法根本搭不上边……写这种题目的时候应当将每个点作为集合,或者说集合中的元素来看,然后再采用合并集合的思想进行考虑。

根据本题还要总结一个教训:对于任何图论题,一定要尽可能将其转化为树论题。因为树的性质和可行操作远远比图要多,处在一个图为树的环境下,显然思路会更加广阔。

无论如何,这题都是一道大写的好题。

可能考虑对于每一个询问,我们都跑一遍单源最短路算法,并且要求从经过边权尽可能大的点来转移。在此基础上进行少许优化,期望得分只有30分。

之后由于要求路径上“最小值最大”,于是我们考虑二分的做法。保留所有不小于枚举的最小值的边,再判断需要的路径是否连通即可。复杂度来说,对于 q q个询问,每次都需要O(logm)O(\log m)的二分,然后判断图连通需要 O(n) O(n),最后总时间复杂度为 O(nqlogm) O(nq\log m)。显然有待改进。

撇开这种做法不谈,我们可以参照NOIP2012_day2_task2_classroom的二分转线性思路,按边权从大到小枚举这个边权。于是接下来只需要在不断合并的过程中,询问的路径由于边的不断加入而趋向连通。当它第一次连通的时候,加入的边权值就是这个询问的答案了。合并操作我们一般采用并查集去完成。如果判定是否询问的两点连通采用直接for过来的方法,时间复杂度为 O(mq×α(n)) O(mq\times\alpha(n)),期望得分60分。

实际上加入了一条边就是将两个端点所在的集合进行合并,我们可以在进行合并操作的时候顺便处理掉询问,然后再把没有处理掉的两个集合的询问合并起来。显然在极端情况下,如果我们把大集合向小集合进行合并,那么大集合内挂的询问被访问的次数就是 O(m) O(m)的,时间复杂度仍然为 O(mq×α(n)) O(mq\times\alpha(n))。

显然我们可以改变这个合并的顺序,使得每一个询问的访问次数都稳定在 O(logm) O(\log m)。这就是所谓的启发式合并了,它的操作就只是将小集合向大集合合并,但是这样可以保证每个点被询问到的复杂度在 log \log级别。于是时间复杂度就降到 O(qlogm×α(n))≈O(qlogm) O(q\log m\times \alpha(n))\approx O(q\log m)。对于启发式合并的证明和做法亦可以参考题目IOI2011_Race

/* 启发式合并做法 */
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
template <class temp>
inline void Rd(temp &res){res=0;char c;while(c=getchar(),c<48);do res=(res<<3)+(res<<1)+(c^48);while(c=getchar(),c>47);
}
inline void Pf(int res){if(!res)return;Pf(res/10);putchar(res%10^48);
}
inline void Pn(int res){if(res<0)putchar('-'),res=-res;if(!res)putchar('0');else Pf(res);putchar('\n');
}
static const int M=50005,Q=30005,N=10005;
//-------------------------
int n,m,q;
struct edge{int u,v,w;bool operator < (const edge &cmp)const{return w>cmp.w;}
}Edges[M];
struct query{int u,v,id;};
vector<query>Mp[N];
int ans[Q],fa[N];
int getfa(int x){if(x==fa[x])return fa[x];else return fa[x]=getfa(fa[x]);
}
void Merge(int u,int v,int dist){//u →v fa[u]=v;for(int j=0,sz=Mp[u].size();j<sz;j++){query now=Mp[u][j];if(~ans[now.id])continue;now.u=getfa(now.u),now.v=getfa(now.v);if(now.u==now.v)ans[now.id]=dist;else Mp[v].push_back(now);}Mp[u].clear();
}
void solve(){for(int i=1;i<=n;i++)fa[i]=i;for(int i=1;i<=m;i++){int u=getfa(Edges[i].u),v=getfa(Edges[i].v);if(u!=v){if(Mp[u].size()>Mp[v].size())Merge(v,u,Edges[i].w);else Merge(u,v,Edges[i].w);}}for(int i=1;i<=q;i++)Pn(ans[i]);
}
int main(){Rd(n),Rd(m);for(int i=1,u,v,w;i<=m;i++){Rd(u),Rd(v),Rd(w);Edges[i]=(edge){u,v,w};}sort(Edges+1,Edges+m+1);Rd(q);memset(ans,-1,sizeof(ans));for(int i=1,u,v;i<=q;i++){Rd(u),Rd(v);query now=(query){u,v,i};Mp[u].push_back(now);Mp[v].push_back(now);}solve();
}

事实上这并不是大众标准解法。

我们接下来是对这些边进行考虑:如果在排除了所有不在同一连通块的询问,那么在统一连通块的询问与加入的边权有什么性质?显然,上述按照边权从大到小加入边的方案,当这部分连通块已经是一棵树的时候,内部的点就已经构成连通了,剩下的边是没有任何用处的。仔细分析这棵树,我们发现它是最大生成树MST(Maximal,滑稽)。于是询问需要在这些最大生成树林上跳跃,所以再套用LCA这个树上的关键点即可,时间复杂度为 O(mlogm×α(n)+(n+q)logn)≈O(mlogm) O(m\log m\times \alpha(n)+(n+q)\log n)\approx O(m\log m)(sort的锅2333)。

/* 最大生成树做法 */
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define finput(x) freopen(x,"r",stdin)
#define foutput(x) freopen(x,"w",stdout)
#define clear(x,val) memset(x,val,sizeof(x))
using namespace std;
template <class temp>
inline void Rd(temp &res){res=0;char c;while(c=getchar(),c<48);do res=(res<<3)+(res<<1)+(c^48);while(c=getchar(),c>47);
}
inline void Pf(int res){if(!res)return;Pf(res/10);putchar(res%10^48);
}
inline void Pn(int res){if(res<0)putchar('-'),res=-res;if(!res)putchar('0');else Pf(res);putchar('\n');
}
static const int M=50005,Q=30005,N=10005,S=15,inf=0x3f3f3f3f;
int n,m,q,pa[N];
struct edge{int u,v,w;bool operator < (const edge &cmp)const{return w>cmp.w;}
}Edges[M];
int getfa(int x){return pa[x]==x?pa[x]:pa[x]=getfa(pa[x]);}
struct node{int to,dist;};//最大生成树林的图
vector<node>G[N];
void add_edge(int u,int v,int w){G[u].push_back((node){v,w});G[v].push_back((node){u,w});
}
void Kruskal(){//O(mlogm)sort(Edges+1,Edges+m+1);for(int i=1;i<=n;i++)pa[i]=i;for(int i=1;i<=m;i++){int u=getfa(Edges[i].u),v=getfa(Edges[i].v);if(u!=v)pa[u]=v,add_edge(Edges[i].u,Edges[i].v,Edges[i].w);}
}
int fa[N],dis[N],dep[N],col[N],color=0;
void dfs(int u,int pre,int d,int c){fa[u]=pre,dep[u]=d,col[u]=c;for(int j=0,sz=G[u].size();j<sz;j++){node now=G[u][j];if(now.to!=pre){dis[now.to]=now.dist;dfs(now.to,u,d+1,c);}}
}
int up(int u,int v){if(col[u]!=col[v])return -1;int dist=inf;while(u!=v){if(dep[u]<dep[v])swap(u,v);dist=min(dist,dis[u]);u=fa[u];}return dist;
}
int main(){Rd(n),Rd(m);for(int i=1,u,v,w;i<=m;i++){Rd(u),Rd(v),Rd(w);Edges[i]=(edge){u,v,w};}Kruskal();for(int i=1;i<=n;i++)if(!dep[i])dfs(i,0,1,++color);Rd(q);for(int i=1,u,v;i<=q;i++)Rd(u),Rd(v),Pn(up(u,v));
}

回到开头那个 O(nqlogm) O(nq\log m)的二分。难道本题就真的不能采用二分做法了么?观察这个二分做法,我们发现复杂度堆积在判断边的联通上,显然对于每个询问,都有许多边被重复删除、重构。于是我们考虑在加入边的时候也同时处理掉每个询问,这个做法也就是所谓的在线改离线算法

对于每一个询问,我们都视作相对独立的一个个小二分块。接下来为了在当前询问前已经合并完所有边权值大于枚举权值的边,我们按照边权值对询问和边混在一起进行排序。遇到边就进行合并,遇到询问就二分,这是非常典型的离线操作的写法。时间复杂度为 O((m+q)logz×α(n)) O((m+q)\log z\times \alpha(n))。

/* 二分+离线(整体二分)算法 */
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define finput(x) freopen(x,"r",stdin)
#define foutput(x) freopen(x,"w",stdout)
#define clear(x,val) memset(x,val,sizeof(x))
using namespace std;
template <class temp>
inline void Rd(temp &res){res=0;char c;while(c=getchar(),c<48);do res=(res<<3)+(res<<1)+(c^48);while(c=getchar(),c>47);
}
inline void Pf(int res){if(!res)return;Pf(res/10);putchar(res%10^48);
}
inline void Pn(int res){if(res<0)putchar('-'),res=-res;if(!res)putchar('0');else Pf(res);putchar('\n');
}
static const int T=100000,M=50005,Q=30005,N=10005,S=18,inf=0x3f3f3f3f;
int n,m,q;
struct edge{int u,v,dist;}Edges[M];
struct node{int u,v,ans;}Asks[Q];
struct query{int l,r,mid,id;/*if id is negative they are Asks .else trey are Edges .note that when trey are edges , l,r is unnecessary .*/bool operator < (const query &cmp)const{if(mid!=cmp.mid)return mid>cmp.mid;return id>cmp.id;}
}res[M+Q];
int fa[N];
namespace union_find_set{int _getfa(int x){if(x==fa[x])return fa[x];else return fa[x]=_getfa(fa[x]);}void _union(int u,int v){u=_getfa(u),v=_getfa(v);if(u!=v)fa[v]=u;}
}
void judge(query &now){using namespace union_find_set;node &pre=Asks[-now.id];if(_getfa(pre.u)==_getfa(pre.v)){pre.ans=now.mid;now.l=now.mid+1;}else now.r=now.mid-1;now.mid=now.l+now.r>>1;
}
int main(){int u,v,w;Rd(n),Rd(m);for(int i=1;i<=m;i++){Rd(u),Rd(v),Rd(w);Edges[i]=(edge){u,v,w};res[i]=(query){0,0,w,i};}Rd(q);for(int i=1;i<=q;i++){Rd(u),Rd(v);Asks[i]=(node){u,v,-1};res[i+m]=(query){0,T,T>>1,-i};}//-------------------------using namespace union_find_set;for(int k=1;k<=S;k++){sort(res+1,res+m+q+1);for(int i=1;i<=n;i++)fa[i]=i;for(int i=1;i<=m+q;i++){int id=res[i].id;if(id>0)_union(Edges[id].u,Edges[id].v);else judge(res[i]);}}for(int i=1;i<=q;i++)Pn(Asks[i].ans);
}

day2

day\;2

1004. 积木大赛

  • 暴力模拟
  • 贪心

一看到这题,首先想到的是每次暴力找到每一段连续区间内的最小值,然后再减去这个基底,这样就不断劈断区间了。但是考虑到那种一直递增的极端情况,感觉它的时间复杂度是 O(n×h) O(n\times h),太暴力过不掉。

于是继续考虑那些可以一次就被删除光的区间,最后发现它的性质是不出现 hi−1>hi h_{i-1}>h_i且 hi<hi+1 h_i的波谷即可。于是在判断的时候还多判断了上述这一点,结果反而只有80分。最后发现它TM有一段很长的相同高度的“波谷”。所以将上述判定改为 hi−1≥hi h_{i-1}\ge h_i且 hi≤hi+1 h_i\le h_{i+1}就过了。

本题唯一的感觉还是贪心玄学吧。并没有想到 O(n) O(n)的解法。甚至也没有想清这个复杂度并不会像快速排序一样退化得那么严重。但是重点还是在没有进行对拍吧,如果考试的时候试一个“5 3 2 2 4”的数据或许就可以查出来了。只要能拍出一个我就会意识到错误……

本题的暴力做法:

  • 每次找到并删去一段连续区间内的最小值,接下来就不断缩小问题规模进行求解。

极限数据是可以卡掉本暴力做法的,只是因为数据比较水40ms都可以过。时间复杂度约为 O(n×h) O(n\times h)。

接下来为了为了进一步优化,我们可以找出那种没有出现“波谷”的情况,即对于这一段区间,无论怎么减去值,都不会劈出新的区间。所以找出“波谷”就可以判掉这种情况。但是这个波谷并不是只有一个点的,它也有可能是一段区间。所以这里处理还会比较麻烦qvq,而且官方数据还会卡这个点。

实际上正解贪心浓缩了上述算法:

  • 我们假设每一次搭建操作都是一条删除线,那么答案就是最少的删除线条数。

    • 当 hi<hi+1 h_i时,显然必须增加删除线,去补充多出来的 hi+1−hi h_{i+1}-h_i部分;
    • 当 hi>hi+1 h_i>h_{i+1}时,有些删除线就必须要终止,接下来只有 hi+1 h_{i+1}条删除线可以存在。

于是只需要计算增加的删除线总数即可,时间复杂度为 O(n) O(n)。

Code:

#include <cstdio>
int main(){int n,pre=0,cnt=0;scanf("%d",&n);for(int i=1,val;i<=n;i++){scanf("%d",&val);if(pre<val)cnt+=val-pre;pre=val;}printf("%d\n",cnt);
}

1005. 花匠

  • 动态规划+数据结构优化
  • 贪心

题意其实就是这样一句话:要求构成“波浪形”的序列,求这个最长序列,且这个长度至少为一。那么我们定义 dp[0][i] dp[0][i]表示这个点作为波谷时的最小值, dp[1][i] dp[1][i]对应表示作为波峰的最小值。于是有转移方程式:

dp[0][i]=max{dp[1][j]∣j∈[1,i),g[j]>g[i]}+1

dp[0][i]=\text{max}\{dp[1][j]\mid j\in [1,i),\;g[j]>g[i]\}+1

dp[1][i]=max{dp[0][j]∣j∈[1,i),g[j]<g[i]}+1

dp[1][i]=\text{max}\{dp[0][j]\mid j\in [1,i),\;g[j]\lt g[i]\}+1

我们按照顺序依次处理可以省掉 [1,i) [1,i)这一维,接下来再将权值扔进数据结构就可以维护了。如果我们不离散权值,根据NOIP2002_day2_Task2那题的经验,在数据过大的情况下,一定要避免使用线段树。我们发现这个查询是前后缀查询,于是我们可以采用树状数组进行判断,常数会小很多。

正解 O(n) O(n)做法基于贪心中的“回撤”思路:

  • 假设当前最优地取到了 g[k] g[k],并且下一个是波峰,则对于下一个元素 k+1 k+1:

    • 若 g[k]<g[k+1] g[k]\lt g[k+1],说明符合题意,可以继续贪心下去。
    • 若 g[k]>g[k+1] g[k]\gt g[k+1],说明之前若选择 g[k+1] g[k+1],则可以取为波峰的情况就更多,于是我们就将最优序列中的 g[k] g[k]替换成 g[k+1] g[k+1]。

同时根据上述贪心,有以下推论:第一个元素是必须要取的。这也是一种很常见的贪心策略。

  • 证明:如果不取第一个元素,就必须有取第k个元素,使得最后得到的解最优(假设下一个要去的元素下标为j)。假设从波谷开始,那么有 g[k]<g[j] g[k]\lt g[j]:

    • 若 g[1]≤g[j] g[1] \le g[j],则不必考虑从 g[k] g[k]转移,因为 [2,k] [2,k]之间可能会产生更多转移;
    • 若 g[1]>g[j] g[1]\gt g[j],那么我们就可以从波峰开始,最后结果必然是 g[j] g[j]的最优解 +1 +1。

同理可证波峰开始的情况。

在缩进了所有相邻元素相同的情况下,不但一定要取最后一个数的贪心可以证明,而且最后的数据整体也就是大波浪形,于是我们在每一个大波浪的谷峰谷底取一下即可。

Code:

#include <cstdio>
#define M 100005
int a[M];
int main(){int n,top=0;scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int i=1;i<=n;i++)if(a[top]!=a[i])a[++top]=a[i];int cnt=0;for(int i=1;i<=top;i++)cnt+=(i==1)||(i==top)||(a[i-1]<a[i]&&a[i]>a[i+1])||(a[i-1]>a[i]&&a[i]<a[i+1]);printf("%d\n",cnt);
}

1006. 华容道

  • 广搜 → \to 最短路

一不小心就敲成dfs了……大概我是受了Spy贪吃蛇那题的刺激,或者没吃早饭饿的眼冒金花的时候一不小心把bfs敲成dfs了吧。qvq

但是如果没有敲dfs我估计也想不到bfs的正解。只可惜暴力的bfs有80分,逼近正解的dfs只有50分,我也是呵呵。以后写图上的搜索题尽量不要写dfs吧,否则今天这一题还是有一搏之力的……

  • 基础搜索算法的选择:深搜dfs or 广搜bfs?

    • 如果这道题目两者方法都通用倒是没什么关系……但是如果作者设计的时候就已经尽可能减少了某一种搜索的剪枝,那没改回来就会浪费起码半小时了……
    • 这道题目采用bfs的理由是:
      1. bfs的实现更加简单,并且在时间系数上要比dfs小(当然需要手写队列实现)(大神才会用dfs魔性剪枝,蒟蒻还是乖乖敲简单的bfs就好了)。
      2. 本题除了dis值以外,我们只需要存储空白点和目标移动点的坐标。显然本题有非常多的重复情况,用bfs在判重上更为方便。(dfs在判重方面简直NPC)
  • 判重部分

    • 注意本题的状态只有两个点的坐标,且坐标范围都很小。对于所有判重方案(mark,Hash+map,set)中,我们可以直接采用四维的mark数组进行标记。这样判重就不带复杂度了。
    • 判断是否到达最终位置的时候不要在扩展节点后再判断,因为bfs的性质,向外扩展更新的时候已经是目前最优解了。那么当它扩展到最终位置后必然最优。充分利用bfs的最优性(相对的,dfs则是保证字典序一定最优)。
  • 算法实现的不同

    • 此处思路可以参见国家集训队2008论文集_肖汉骏例题2-2,与本题华容道是一样的。
    • 我们如果以空白点作为基准,搜索它的移动,那么就是标准的暴力bfs,只需要判断空白点下一步是否为目标点即可。期望得分80分。
    • 如果我们以目标节点作为基准,搜索它的移动,那么就会发现性质:首先根据华容道游戏本身的性质,当起始棋子开始移动后,空白点与它相邻;其次,在起始棋子开始移动后,在它接下来的移动中,四周空白点如何实现这一移动的次数是固定的。根据上述性质,我们将这个实现次数转化成边权起始棋子移动的边权。因为边权不为1不能跑bfs,所以我们跑最短路

四周空白点处理的时候,我的处理方法是:保存空白点坐标+起始棋子的相对方向+目标位置相对于起始棋子的方向的四维组。

代码一交,发现WA在大数据上。后来灵光一现觉得可能是溢出的问题,接着也发现是溢出在预处理的地方,于是改大一点范围就过了。

讲真代码实现的比较恶心,mark数组和Dijkstra部分还能再优化的。但是再调下去我就是只废铅了qvq。

Code:

#include <cstdio>
#include <cstring>
#include <cassert>
#include <algorithm>
#include <queue>
#define finput(x) freopen(x,"r",stdin)
#define foutput(x) freopen(x,"w",stdout)
#define clear(x,val) memset(x,val,sizeof(x))
#define N 32
#define M 800005
using namespace std;
template <class temp>
inline void Rd(temp &res){res=0;char c;while(c=getchar(),c<48);do res=(res<<3)+(res<<1)+(c^48);while(c=getchar(),c>47);
}
template <class temp>
inline bool check(temp &a,temp b){if(~a&&a<b)return false;a=b;return true;
}
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
#define fi first
#define se second
//-----------------------------
typedef pair<int,int> pii;
pii goal;
int pic[N][N],n,m,kase;
bool judge(int x,int y){return x<=n&&x&&y<=m&&y&&pic[x][y];
}
bool mark[N][N][N][N];
int dp[N][N][4][4];
struct node{int x1,y1;//起始棋子位置 int x2,y2;//空白点的位置 int dis;bool operator < (const node &cmp)const{return dis>cmp.dis;}
};
//-----------------------------
int dist[N][N];
int direction(int x1,int y1,int x2,int y2){//node1->node2//node 2 相对于node 1所在位置int xd=x2-x1,yd=y2-y1;int d;for(d=0;d<4;d++)if(dx[d]==xd&&dy[d]==yd)return d;assert(d<4);
}
pii que[M];
void bfs(node st){//先让白色棋子与起始棋子相邻(白色棋子走向起始棋子)pic[st.x1][st.y1]=0;clear(dist,-1);dist[st.x2][st.y2]=0;int L=0,R=-1;que[++R]=pii(st.x2,st.y2);while(L<=R){pii now=que[L++];for(int d=0;d<4;d++){pii nxt=pii(now.fi+dx[d],now.se+dy[d]);if(!judge(nxt.fi,nxt.se)||~dist[nxt.fi][nxt.se])continue;dist[nxt.fi][nxt.se]=dist[now.fi][now.se]+1;que[++R]=nxt;}}pic[st.x1][st.y1]=1;
}
void init(){for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)//枚举blank点 if(pic[i][j])for(int d=0;d<4;d++){//枚举起始棋子点 pii nxt=pii(i+dx[d],j+dy[d]);if(!judge(nxt.fi,nxt.se))continue;bfs((node){nxt.fi,nxt.se,i,j,0});for(int k=0;k<4;k++)//枚举下一个转移点 if(!~dist[nxt.fi+dx[k]][nxt.se+dy[k]])dp[i][j][d][k]=-1;else dp[i][j][d][k]=dist[nxt.fi+dx[k]][nxt.se+dy[k]]+1;}
}
int Dijkstra(node st){if(st.x1==goal.fi&&st.y1==goal.se)return st.dis;priority_queue<node>q;bfs(st);for(int d=0;d<4;d++){int x=st.x1+dx[d],y=st.y1+dy[d];if(~dist[x][y])q.push((node){st.x1,st.y1,x,y,dist[x][y]});}while(!q.empty()){node now=q.top();q.pop();if(now.x1==goal.fi&&now.y1==goal.se)return now.dis;if(mark[now.x1][now.y1][now.x2][now.y2])continue;mark[now.x1][now.y1][now.x2][now.y2]=true;int k=direction(now.x2,now.y2,now.x1,now.y1);for(int d=0;d<4;d++){int x=now.x1+dx[d],y=now.y1+dy[d];assert(d==direction(now.x1,now.y1,x,y));if(!judge(x,y)||!~dp[now.x2][now.y2][k][d])continue;q.push((node){x,y,now.x1,now.y1,now.dis+dp[now.x2][now.y2][k][d]});}}return -1;
}
int main(){Rd(n),Rd(m),Rd(kase);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)Rd(pic[i][j]);clear(dp,-1);init();while(kase--){clear(mark,0);node st;Rd(st.x2),Rd(st.y2),Rd(st.x1),Rd(st.y1);st.dis=0;Rd(goal.fi),Rd(goal.se);printf("%d\n",Dijkstra(st));}
}

NOIP2013 提高组复赛解题报告相关推荐

  1. NOIP2016提高组复赛解题报告

    Day1 T1-玩具谜题 Day1 T2-天天爱跑步 Day1 T3-换教室 Day2 T1-组合数问题 Day2 T2-蚯蚓 Day2 T3-愤怒的小鸟 转载于:https://www.cnblog ...

  2. C语言普及组NOIP考试培训,NOIP 2018普及组复赛解题报告来了!

    以下解题思路及选手代码未经官方评测,仅供参考,复赛成绩以官方(CCF)评测结果为准. 主要考察字符串.参考代码: #include int main() { int ans = 0; char ch; ...

  3. 洛谷-求同构数的个数-NOIP2013提高组复赛

    题目描述 Description 所谓同构数是指这样的数,即它出现在它的平方数的右端.例如,5的平方是25 (即5×5=25),5是25右端的数,那么5就是同构数.又如,25的平方是625(即25×2 ...

  4. NOIP2018提高组Day1 解题报告

    前言 关于\(NOIP2018\),详见此博客:NOIP2018学军中学游记(11.09~11.11). 这次\(NOIP\ Day1\)的题目听说很简单(毕竟是三道原题),然而我\(T3\)依然悲剧 ...

  5. 【题解】P1979 [NOIP2013 提高组] 华容道(SPFA,BFS,常数优化)

    [题解]P1979 [NOIP2013 提高组] 华容道 最近打比赛次次挂..平均每周得被至少一场比赛打击一次(这周好不容易 ABC 打的还行模拟赛又挂--)心烦意乱.写篇题解疏散一下内心的苦闷(雾) ...

  6. NOIP2010 提高组 复赛 translate 机器翻译

    NOIP2010 提高组 复赛 translate 机器翻译 1.读题,很快弄明题意,单词不在内存中就查字典,统计查字典次数. 2.内存采用队列方式.统计进队列次数,即为查询次数. 3.程序很快编好, ...

  7. 【NOIP2013提高组】花匠

    题目背景 NOIP2013 提高组 Day2 试题. 题目描述 花匠栋栋种了一排花,每株花都有自己的高度.花儿越长越大,也越来越挤.栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空 ...

  8. 【NOIP2013提高组】火柴排队

    题目背景 NOIP2013 提高组 Day1 试题 题目描述 涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度.现在将每盒中的火柴各自排成一列,同一列火柴的高度互不相同,两列火柴之间的距离定 ...

  9. P1979 [NOIP2013 提高组] 华容道

    题目来源 [NOIP2013 提高组] 华容道 - 洛谷 题目考点 搜索   图论 题目 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, ...

最新文章

  1. 用随机森林分类器和GBDT进行特征筛选
  2. springboot学习笔记(五)
  3. nginx定制header返回信息模块ngx_headers_more
  4. 20121108团队博客(苏若)
  5. python image 转成字节_就是这么牛!三行Python代码,让数据处理速度提高2到6倍
  6. 前端学习(763):变量属性函数方法的区别
  7. java面试之HashMap延伸出来的--什么是ConcurrentHashMap?
  8. u-boot移植随笔:让u-boot shell支持tab、命令历史
  9. inno setup相关 (二)
  10. 如何在 Linux 中找到你的 公网IP 地址
  11. android wifi控制手机屏幕,如何将手机屏幕无线投射到电脑 电脑反向控制手机屏幕的详细教程...
  12. matlab ctradon函数,Radon变换入门matlab CT原理
  13. alsa 调试工具 aplay 的 编译与 使用
  14. 如何屏蔽油管网页上的广告
  15. 【Trailhead题目解析】Prepare your salesforce org for users - 2Update the Exchange Rate with ACM
  16. 谷歌命名工具_Google地图正在重命名整个社区
  17. 成功的道路并不拥挤,别那么早放弃
  18. Java创建图片并绘图
  19. 提升深度学习模型的表现,你需要这20个技巧
  20. apt update Updating from such a repository can‘t be done securely, and is therefore

热门文章

  1. 如果我能天天24小时不睡觉那该多好?
  2. HDU 4966 GGS-DDU
  3. Pytorch小抄宝典!
  4. 手机充值的功能测试框架-测试要点
  5. bzoj3028 食物 (普通型生成函数)
  6. 怎么看服务器的内部性能参数,怎么看服务器详细配置参数
  7. 解析创客教育的高中机器人教学实践
  8. 【Appium】元素探测工具UIautomatorViewer 和Appium destop内置工具
  9. 获取分钟数为5的倍数的时刻
  10. LeetCode刷题(python版)——Topic10盛最多水的容器