CF1495B Lets Go Hiking
tag:博弈论
一个简单粗暴的思路,枚举每一个初始位置,然后判断可行性。
首先如果先手只能往一个方向走,那么后手直接堵旁边就赢了,所以只考虑先手两边都可以走的情况。
那么对于后手来说,他有两种选择:
-
和先手比长度
-
堵路
设先手能走到的范围为 \([l,r]\)。
对于第一种情况,相当于二者互不干扰,则初始位置 \(i\in[1,l)\) 或 \((r,n]\),或者从 \(l\) 往左走,从 \(r\) 往右走。
而堵路就两种情况,\(l\) 往右走和 \(r\) 往左走,显然后手会走长的那一条。
至于先手/后手往左/右能走多远,可以先扫一遍预处理 \(O(n)\)。
for(register int i=1, lst=1; i<=n; i++)
if(a[i]>a[i+1] or i==n){
for(register int j=lst; j<=i; j++) up[1][j] = i, down[0][j] = lst;
lst = i+1;
}
for(register int i=n, lst=n; i; i--)
if(a[i]>a[i-1] or i==1){
for(register int j=lst; j>=i; j--) up[0][j] = i, down[1][j] = lst;
lst = i-1;
}
后手在前缀/后缀中能走的最远长度也可以扫一遍预处理 \(O(n)\)。
for(register int i=1; i<=n; i++) pre[i] = max(pre[i-1],max(i-up[0][i]+1,up[1][i]-i+1));
for(register int i=n; i>=1; i--) suf[i] = max(suf[i+1],max(i-up[0][i]+1,up[1][i]-i+1));
然后枚举一下先手的初始位置 \(O(1)\)判断,也是 \(O(n)\)。
for(register int i=1; i<=n; i++) if(down[0][i]!=i and down[1][i]!=i){
int len1 = i-down[0][i]+1, len2 = down[1][i]-i+1;
int l = down[0][i], r = down[1][i];
if(max(max(pre[l-1],l-up[0][l]+1),max(suf[r+1],up[1][r]-r+1))>=max(len1,len2)) continue;
if(((len1>>1)<<1)>=len2) continue;
if(((len2>>1)<<1)>=len1) continue;
ans++;
}
注意一个小细节,如果要堵路,那么要保证两者间的距离为偶数,如果为奇数的话先手就能反杀。(模拟一下不难发现)