自己的AC做法似乎离正解偏了十万八千里而且复杂了不少……不管怎样还是记录下来吧。

题意:

题目链接:

JOISC2017 F - AtCoder

JOISC2017 F - LOJ

\(N\)个车站排成一行,第\(i\)个车站有一个在\([1,M]\)之间的权值\(l_i\)。有\(M\)种列车,第\(i\)种在所有满足\(l_j \geq i\)的车站\(j\)停靠。如果搭乘一种列车从\(u\)到\(v\),需要在\(u\)、\(v\)之间的所有停靠站(包含\(u\)和\(v\)),各停车一次,定义这段行程的代价为(\(停车次数-1\))。如果列车\(i\)和\(j\)都在车站\(k\)停靠,允许在车站\(k\)从\(i\)换乘到\(j\)。列车行驶的方向任意。\(Q\)次询问,每次给出起点\(s\)和终点\(t\),问从\(s\)坐车到\(t\)的最小总代价。

\(1 \leq N,M,Q \leq 10^5\)

思路:

首先试着构出一张无向图,在可以用代价\(1\)转移的点之间连边。

构图方案:

1、标记所有\(l_i=M\)的车站\(i\),在相邻(中间没有被标记车站)的被标记车站之间连边。

2、新标记所有\(l_i=M-1\)的车站\(i\)(保留先前标记),相邻的被标记车站连边。

3、新标记所有\(l_i=M-2\)的车站\(i\)(保留先前标记),相邻的被标记车站连边。

……

注意到我们标出的这些边形成了一张分层图,其中第\(i\)层的边集就是在以上构图方法中,第\(M+1-i\)步添加的边。不妨称序号大的层级为高层。

如果把第\(i\)层从点\(j\)到点\(k\)的边,画成平面上一条从\((j,i)\)到\((k,i)\)的线段,那么对第一个样例画出的图是:

注意到性质:高层线段的端点集合,一定是低层线段端点集合的子集;高层的线段,一定完整包含了低层对应位置的线段,且线段之间要么包含要么相离。

此外易见,对每一个\(i\),存在点\(i\)作为端点的最高层级是\(l_i\)。

于是问题转化成,在这张分层图上,要从\((s,1)\)走到\((t,1)\),允许以下两种转移,问最小代价:

1、如果点\(a\)是层级\(b\)和\(b+1\)的端点,则以代价\(0\)在\((a,b)\)与\((a,b+1)\)之间双向转移。

2、如果点\(a\)不是层级\(b\)中最靠左的端点,且在层级\(b\)中,点\(a\)左侧的相邻端点是\(c\),则以代价\(1\)在\((a,b)\)与\((c,b)\)之间双向转移。

接下来就是喜闻乐见的推结论时间了!

结论1:一定有一条最优路径,满足其经过的层级高度是按访问顺序先增后减的。

证明:

考虑对于起终点相同,横坐标都单调递增(减)的两条路线\(A\)和\(B\),如果\(A\)的高度始终不低于\(B\),那么\(A\)经过的边数一定不大于\(B\)。

理由是:“高层的线段,一定完整包含了低层对应位置的线段”,所以走高层一定不会更差。

接下来考虑反证,假设这个路径的某一段形如下图的黑色路线:

那么由先前结论,一定可以在达到最高点后改走红色路线,使答案不变得更差,同时保持凸性。

对于黑色路线横坐标不单调的情况,用类似方法可以证明同样的结论,有些繁琐,在此略去。

结论2:记\(L(a,b)\)为层级\(b\)中,点\(a\)左侧最近的端点(当\(a\)是端点时\(L(a,b)\)=\(a\));\(R(a,b)\)为层级\(b\)中,点\(a\)右侧最近的端点。那么,从\((a,1)\)到\(L(a,b)\)(或者\(R(a,b)\))的最短路径,在第\(i\)层首次经过的点一定是\(L(a,i)\)或\(R(a,i)\)。

这个结论之后再证。

结论3:对\(s<t\),从\((s,1)\)到\((t,1)\)的最短路径所经过的最大层级,要么是数组\(l\)在下标区间\([s,t]\)上的最大值\(Max\),要么是\(Max+1\)。

对于结论2、3的证明:

考虑在下图中,当前位于点\(A\),路径的起终点是第一层的\(s\)和\(t\),且现在处于路径的上升阶段。

图中\(B=L(A,i+1)\),\(C=R(A,i+1)\)。那么图中从\(A\)到\(t\)有以下几种可能的路径:

1、黑色路径:在第\(i\)层向右走直到横坐标超过\(C\)的横坐标,再沿着最优路径走到\(t\)。

2、褐色路径:在第\(i\)层向右走,再向上走到\(C\),再沿着最优路径走到\(t\)。

3、深蓝+褐色路径:沿着深蓝色路径经过\(B\)走到\(C\),再沿着褐色路径的后一半走到\(t\)。

对于起终点相同,横坐标都单调递增(减)的两条路线\(A\)和\(B\),如果\(A\)的高度始终不低于\(B\),那么\(A\)经过的边数一定不大于\(B\)。

所以,路径2一定不长于路径1。

换句话说,只要存在点\(C\)在\(i+1\)层,且\(C\)的横坐标在\(s\)和\(t\)之间,走到\(i+1\)层一定比留在第\(i\)层更优。

再换句话说,最短路径经过的最大层级,不小于\(Max\)。结论3的后一半之后再证。

接着考虑用数学归纳证明结论2。

归纳假设:最优路径在第\(i\)层首次经过的点一定是\(L(a,i)\)或\(R(a,i)\)。

当\(i=1\)时:易证。

当\(i=k\)时:

图中的\(A\)为\(L(a,k)\)或\(R(a,k)\),则易知\(B=L(a,k+1)\),\(C=R(a,k+1)\)。

对于在\(C\)右侧的\(C'\),在第\(i+1\)层首次经过的点是\(C'\)的路径,一定是路径1,不是最优路径;

对于在\(B\)左侧的\(B'\),如果一条路径在\(i+1\)首先经过的点是\(B'\):

1°:如果到达\(B'\)后向右行走,则这条路径一定不优于图中的路径3(起终点相同,代价却更大);

2°:如果到达\(B'\)后向左/上行走,易证,从\((B,k+2)\)向左走,一定能走到同样的第\(k+2\)层的结点,且代价不增加。

综上,在第\(k+1\)层经过的第一个点,必为\(B\)或\(C\)。

所以,如果\(i=k\)时命题成立,则对于\(i=k+1\)也成立。

结论2证毕。

由结论2可知,在最高层\(h\),如果\(h>Max\),在这一层一定只会走一步:从\(L(s,h)\)(或\(R(s,h)\))到\(L(t,h)\)(或\(R(t,h)\)),代价恒为1。

又因为\(L(s,Max+2) \leq L(s,Max+1) , R(s,Max+2) \geq R(s,Max+1)\)(对\(t\)同理),所以\(h=Max+2\)时的代价不小于\(h=Max+1\)时的代价。

同理易证第\(Max+1\)层不比任何更高层更差。

所以最高层数不小于\(Max\),不大于\(Max+1\),结论3证毕。

呼……推完结论,终于开始设计算法了。

首先,回答查询的时候,显然应该枚举最高层级\(h\)(\(Max\)到\(Max+1\)),再把以下三个数相加:

1、\(s\)走到这一层的最小代价;

2、\(t\)走到这一层的最小代价;

3、\(s\)、\(t\)在这一层会合的最小代价;

(当然,\(s\)、\(t\)在最高层的首次访问位置是\(L(...,h)\)还是\(R(...,h)\),还要对这个分四类讨论)

第三项很好求,在此只讲前两项的求法。

这种不断向一个方向跳的问题,不是一看就是倍增吗

考虑倍增,\(dp[i][j]\)表示从\((j,1)\)到\(L(j,2^i)\)和\(R(j,2^i)\)的距离?

等等,不对啊……这玩意怎么转移啊……

倍增算法可行的前提是,如果你把跳\(2^i\)步拆成跳两次\(2^{i-1}\)步,则前一步跳到的中继点,必须有一个状态来代表。

试了好几种比较裸的倍增方法,都不可行……看来得再分析一波了。

等等,其实我们关心的是\((i,1)\)与\(L(i,j)\)、\(R(i,j)\)之间的距离,而我们可以用二元组\(\{L(i,j),R(i,j)\}\)来代替状态中的\(i\)?

具体地说,这里,二元组\(\{L,R\}\)表示“当前所在位置的行(层级)不确定,(在结论2中的)列序号可以是\(L\)也可以是\(R\)”。

hint:注意每一个二元组都只在一个层级区间存在,即对于确定的\(\{L(i,j),R(i,j)\}\),\(j\)的可能值是一个区间。

而且发现这种二元组只有\(O(N)\)个?

(证明方法,二元组数量就是构出的图中的不同的边数,而构图时每标记一个新点,最多产生两条新边。)

而且在不断往上跳的过程中,每一个二元组\(P\)的后继是唯一的?(后继是说,从\(P\)往上跳的过程中,达到的第一个与自己不相等的二元组)

而且只有在从二元组\(P\)跑到\(P\)的后继时才会产生代价?(也就是说同一个二元组代表的所有状态代价一样)

于是想到:\(dp[i][P]\)表示,从二元组\(P\)开始往上跳,所在的二元组改变\(2^i\)次(即当前在\(P\)的第\(2^i\)个后继)后的最小代价。

hint:注意\(dp[i][P]\)是两个值,分别表示到达二元组的左侧、右侧结点的代价。

前一步跳到的中继点,必须有一个状态来代表。

满足了吗?满足了。

啥?你说\(N^2 \log M\)大小的数组开不下?那是你的问题咯……好好想想怎么高效的把二元组放进状态里。

查询的时候沿着倍增指针不断地跳,直到当前二元组的存在区间囊括了目标层级为止。

总时间复杂度\(O((N+Q) \log M)\)。

接下来就是愉快的代码时间啦!

代码:

#include <bits/stdc++.h>
using namespace std;
#define iinf 2000000000
#define linf 1000000000000000000LL
#define ulinf 10000000000000000000ull
#define MOD1 1000000007LL
#define mpr make_pair
typedef long long LL;
typedef unsigned long long ULL;
typedef unsigned long UL;
typedef unsigned short US;
typedef pair < int , int > pii;
clock_t __stt;
inline void TStart(){__stt=clock();}
inline void TReport(){printf("\nTaken Time : %.3lf sec\n",(double)(clock()-__stt)/CLOCKS_PER_SEC);}
template < typename T > T MIN(T a,T b){return a<b?a:b;}
template < typename T > T MAX(T a,T b){return a>b?a:b;}
template < typename T > T ABS(T a){return a>0?a:(-a);}
template < typename T > void UMIN(T &a,T b){if(b<a) a=b;}
template < typename T > void UMAX(T &a,T b){if(b>a) a=b;}
const int lg=17,inf=1000000;
vector < int > prt[100005],lev[100005];
int n,m,q,l[100005],mx[lg][100005],dis[2][100005],nx[100005];
struct data{
int ld,rd,lc,rc;
void set(int LD,int RD,int LC,int RC){
ld=LD;rd=RD;lc=LC;rc=RC;
if(!ld) lc=inf;
}
void tran(data L,data R){
if(L.ld==-1) L.lc=L.rc=inf;
if(R.ld==-1) R.lc=R.rc=inf;
int nlc=MIN(lc+L.lc,rc+R.lc),nrc=MIN(lc+L.rc,rc+R.rc);
set((L.ld==-1?R.ld:L.ld),(L.ld==-1?R.rd:L.rd),nlc,nrc);
}
};
vector < data > dp[lg][100005][2];
int prev(int i,int l){
// before or equal
int j;
for(j=lg-1;j>=0;--j){
if(i-(1<<j)>=-1 && mx[j][i-(1<<j)+1]<l){
i-=(1<<j);
}
}
return i;
}
int nxt(int i,int l){
// after
int j;
++i;
for(j=lg-1;j>=0;--j){
if(i+(1<<j)<=n && mx[j][i]<l){
i+=(1<<j);
}
}
return i;
}
int qrymax(int l,int r){
int le;
for(le=0;(1<<(le+1))<r-l+1;++le);
return MAX(mx[le][l],mx[le][r-(1<<le)+1]);
}
int qrydis(int s,int t,int le){
int cnt=0;
if(s>t) swap(s,t);
while(s<t){
if(le==l[s]){
cnt+=dis[1][s]%inf;
s=nxt(s,le+1);
}
else{
++cnt;
s=nxt(s,le);
}
}
if(t<s && le==l[t]){
cnt-=dis[1][t]%inf;
}
return cnt;
}
data qrydisV(int s,int le){
data cur,nxt;
if(s==n-1)
cur.set(s-1,0,1,0);
else
cur.set(s,0,0,1);
int i;
for(i=lg-1;i>=0 && cur.ld!=-1;--i){
nxt=cur;
nxt.tran(dp[i][nxt.ld][0][nxt.rd],dp[i][nxt.ld][1][nxt.rd]); if(nxt.ld!=-1 && (nxt.rd?lev[nxt.ld][nxt.rd-1]:-1)<le)
cur=nxt;
if(cur.ld!=-1 && lev[cur.ld][cur.rd]>=le) break;
}
if(cur.ld!=-1 && lev[cur.ld][cur.rd]<le) cur.ld=-1;
return cur;
}
int main(){
// inputting start
// 数据结构记得初始化! n,m别写反!
int i,j,k,fsv=-1,fsp=-1,scv=-1;
scanf("%d%d%d",&n,&m,&q);
for(i=1;i<=n;++i){
scanf("%d",l+i);
--l[i];
if(l[i]>fsv){
fsv=l[i];
fsp=i;
}
else if(l[i]>scv){
scv=l[i];
}
}
l[fsp]=scv;
l[0]=m++;
++n;
#ifdef LOCAL
TStart();
#endif
// calculation start
// 数据结构记得初始化! n,m别写反!
for(i=0;i<n;++i) mx[0][i]=l[i];
for(i=1;i<lg;++i){
for(j=0;j<n;++j){
int nl=j+(1<<(i-1));
if(nl<n)
mx[i][j]=MAX(mx[i-1][j],mx[i-1][nl]);
else
mx[i][j]=mx[i-1][j];
}
}
for(i=n-1;i>=0;--i){
nx[i]=nxt(i,l[i]+1);
int suc=nxt(i,l[i]);
if(suc>=n)
dis[1][i]=inf;
else if(l[suc]>l[i])
dis[1][i]=1;
else
dis[1][i]=dis[1][suc]+1; for(j=i+1;j<suc;j=nx[j]){
prt[i].push_back(j);
lev[i].push_back(l[j]);
}
if(suc<n){
prt[i].push_back(suc);
lev[i].push_back(l[i]);
} dp[0][i][0].resize(prt[i].size());
dp[0][i][1].resize(prt[i].size());
for(j=0;j<(int)prt[i].size()-1;++j){
dp[0][i][0][j].set(i,j+1,0,1);
dp[0][i][1][j].set(i,j+1,1,MIN(2,dis[1][prt[i][j]]));
}
}
for(i=0;i<n;++i){
int prd=prev(i,l[i]+1),prv=prev(i-1,l[i]),d=(int)prt[i].size()-1;
if(prv==-1)
dis[0][i]=inf;
else if(l[prv]>l[i])
dis[0][i]=1;
else
dis[0][i]=dis[0][prv]+1; if(d==-1) continue;
if(prd!=-1 && lev[prd].back()<=l[i]) prd=prev(prd-1,l[i]+1); if(prd==-1 || lev[prd].empty() || lev[prd].back()<=l[i])
dp[0][i][0][d].ld=dp[0][i][1][d].ld=-1;
else{
int nd=lower_bound(lev[prd].begin(),lev[prd].end(),l[i]+1)-lev[prd].begin();
int disL=dis[0][i],disR=dis[1][prt[i][d]];
if(prt[i][d]==prt[prd][nd]) disR=0; dp[0][i][0][d].set(prd,nd,MIN(disL,disR+2),MIN(disL+1,disR+1));
dp[0][i][1][d].set(prd,nd,MIN(disR+1,disL+1),MIN(disR,disL+2));
}
}
for(i=1;i<lg;++i){
for(j=0;j<n;++j){
dp[i][j][0].resize(prt[j].size());
dp[i][j][1].resize(prt[j].size());
for(k=0;k<(int)prt[j].size();++k){
int d;
for(d=0;d<2;++d){
data &trs=dp[i-1][j][d][k],&cur=dp[i][j][d][k];
if(trs.ld==-1)
cur.ld=-1;
else{
cur=trs;
cur.tran(dp[i-1][cur.ld][0][cur.rd],dp[i-1][cur.ld][1][cur.rd]);
}
}
}
}
}
while(q--){
int s[2];
scanf("%d%d",s,s+1);
sort(s,s+2); int mxl=qrymax(s[0],s[1]),res=inf;
for(i=mxl;i<=mxl+1;++i){
data tr[2];
for(j=0;j<2;++j){
tr[j]=qrydisV(s[j],i);
if(tr[j].ld==-1) break;
}
if(j<2) break; int p[2],d[2];
for(j=0;j<2;++j){
p[0]=(j?prt[tr[0].ld][tr[0].rd]:tr[0].ld);
d[0]=(j?tr[0].rc:tr[0].lc);
for(k=0;k<2;++k){
p[1]=(k?prt[tr[1].ld][tr[1].rd]:tr[1].ld);
d[1]=(k?tr[1].rc:tr[1].lc);
UMIN(res,d[0]+d[1]+qrydis(p[0],p[1],i));
}
}
}
printf("%d\n",res-1);
}
#ifdef LOCAL
TReport();
#endif
return 0;
}

JOI2017 春季合宿:Railway Trip的更多相关文章

  1. UOJ356 [JOI2017春季合宿] Port Facility 【启发式合并】【堆】【并查集】

    题目分析: 好像跑得很快,似乎我是第一个启发式合并的. 把玩具看成区间.首先很显然如果有两个玩具的进出时间有$l1<l2<r1<r2$的关系,那么这两个玩具一定在不同的栈中间. 现在 ...

  2. [JOI2017春季合宿]Port Facility[set、二分图]

    题意 你有两个栈,有 \(n\) 个货物,每个货物有一个进栈时间和出栈时间(所有时间的并集是1~2n),问有多少种不同的入栈方案. \(n\le 10^6\) 分析 把每个货物的存在看成区间,相交的区 ...

  3. UOJ #356. 【JOI2017春季合宿】Port Facility

    Description 小M有两个本质不同的栈. 无聊的小M找来了n个玩具.之后小M把这n个玩具随机顺序加入某一个栈或把他们弹出. 现在小M告诉你每个玩具的入栈和出栈时间,现在她想考考小S,有多少种方 ...

  4. UOJ #357. 【JOI2017春季合宿】Sparklers

    Description 小S和小M去看花火大会. 一共有 n 个人按顺序排成一排,每个人手上有一个仅能被点燃一次的烟花.最开始时第 K 个人手上的烟花是点燃的. 烟花最多能燃烧 T 时间.每当两个人的 ...

  5. UOJ356 【JOI2017春季合宿】Port Facility

    暴力就是O(n^2)连边,二分图,这样只有22分. 我们考虑优化建边,我们按照左端点排序,对于一个新加进来的线段,我们向左端点距其最近的和他相交的线段连边,别的相交的我们连同色边,当一个点连了两条同色 ...

  6. 【JOI2017春季合宿】Port Facility

    http://uoj.ac/problem/356 题解 思路和\(NOIP\)双栈排序差不多. 对于两个元素,若\(l_1<l_2<r_1<r_2\)那么它们不能在一个栈里,我们连 ...

  7. BZOJ 4388 [JOI2012春季合宿]Invitation (线段树、二叉堆、最小生成树)

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=4388 题解 模拟Prim算法? 原题所述的过程就是Prim算法求最大生成树的过程.于是我 ...

  8. BZOJ 4221 [JOI2012春季合宿]Kangaroo (DP)

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=4221 题解 orz WYC 爆切神仙DP 首先将所有袋鼠按大小排序.考虑从前往后DP, ...

  9. LOJ #2731 [JOI2016春季合宿]Solitaire (DP、组合计数)

    题目链接 https://loj.ac/problem/2731 题解 首先一个很自然的思路是,设\(dp[i][j]\)表示选了前\(i\)列,第\(2\)行第\(i\)列的格子是第\(j\)个被填 ...

随机推荐

  1. 腾讯云微信小程序域名变更指南

    1 . 将域名添加到云解析里面, 将解析的地址指向已有的小程序负载均衡地址: https://console.qcloud.com/cns 将域名添加到解析列表 添加成功之后,点击解析按钮,添加二级域 ...

  2. 7.Zabbix 3.0 web监控

    请查看我的有道云笔记: http://note.youdao.com/noteshare?id=5f6b67f98a802fb831a83d810969c583&sub=B8D5267BDD5 ...

  3. May 14th 2017 Week 20th Sunday

    A smooth sea never made a skillful mariner. 平静的海洋练不出熟练的水手. A smooth sea never made a skillful marine ...

  4. bzoj5029 贴小广告

    Description 现在有一堵墙,墙上分为若干个单元.接下来会来n个人在墙上贴小广告.每次每个人选择墙上连续一段的单元贴上自己公司与众不同的小广告.因为小广告可能会出现被覆盖的情况,由于公司之间存 ...

  5. POJ 2704 Pascal's Travels 【DFS记忆化搜索】

    题目传送门:http://poj.org/problem?id=2704 Pascal's Travels Time Limit: 1000MS   Memory Limit: 65536K Tota ...

  6. JAVA程序员常用软件类库下载地址整理

    IT行业是个发展更新特别快的领域,每个程序员在职业生涯中都会经历各类技术和工具的更新迭代. 俗话说:工欲善其事,必先利其器.本着独乐乐不如众乐乐的精神(其实是自己健忘)本人把经常要用到的软件工具官方下 ...

  7. 【luogu P2251 质量检测】 题解

    也是一道ST表的板子题,很裸的RMQ问题,只需要在查询区间时处理一下下就好啦~ #include <cstdio> #include <algorithm> using nam ...

  8. python 并发编程之协程

    一.协程 协程: 单线程下的并发,又称 微线程.协程是一种用户态的的轻量级线程,即协程是由用户程序自己控制调度的. ​ 协程的本质就是在单线程下,由用户自己控制一个任务,遇到 io 阻塞就切换另外一个 ...

  9. 简析--Java中常见的一些关键字的解析

    在Java开发中我们经常会用到一些关键字,关键字的定义很有意思"Java事先定义好的,具有特殊含义的单词",那么我们怎么来用好关键字呢?下面我们对一些常见的关键字进行分析和比较; ...

  10. Python常用模块之time和datetime

    1.时间的格式化 结构化时间 ##把字符串时间转换成结构化时间 time.strptime("2017-06-21","%Y-%m-%d") ##把结构化时间转 ...