A. 旋转子段
对于位置i上的数$a_i$,易知有且仅有一个旋转点使它旋转到$a_i$,这个旋转点是$\frac{i+a_i}{2}$
因为旋转点分落在点上的旋转点和两点之间的旋转点,除2不易处理。
不妨将位置i上的数存放在$i+a_i$处。
设1~i的原本固定点为$pre_i$个,后缀同理为$suf_i$,这两条信息可以预处理。
那么问题转化为找一个区间,使得
$pre_{l-1}+suf_{r+1}+sum_{l+r}$最大。
$sum_{l+r}$的意思为对于l+r旋转点,范围l,r内能以它为旋转点变为固定点的个数。
考虑每个旋转点,当它向两边拓展一个长度。
仅在这个长度上存在至少一个会被旋转到固定点的点时能使答案更优。
所以将同一旋转点的一些点压进vector,
枚举每个旋转点vector里的每个点,二分查找它对称的点的坐标,
尝试更新答案。
(二分查找的操作是单调的,单调指针也可做,懒得打)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<vector> 5 #include<algorithm> 6 using namespace std; 7 const int N=500100; 8 int n,ans=0,a[N],pre[N],suf[N]; 9 vector<int> ve[2*N]; 10 int main(){ 11 scanf("%d",&n); 12 for(int i=1;i<=n;++i){ 13 scanf("%d",&a[i]); 14 ve[i+a[i]].push_back(i); 15 pre[i]=pre[i-1]+(a[i]==i); 16 } 17 for(int i=n;i;--i) suf[i]=suf[i+1]+(a[i]==i); 18 ans=pre[n]; 19 for(int i=1;i<=2*n;++i){ 20 if(ve[i].empty()) continue; 21 if(i&1){ 22 int p=i+1>>1;//右侧 23 for(int j=0;j<ve[i].size();++j){ 24 if(ve[i][j]<p){ 25 int s=p+(p-ve[i][j])-1; 26 ans=max(ans,upper_bound(ve[i].begin(),ve[i].end(),s)-ve[i].begin()-j+pre[ve[i][j]-1]+suf[s+1]); 27 } 28 if(ve[i][j]>=p){ 29 int s=p-(ve[i][j]-p)-1; 30 ans=max(ans,j+ve[i].begin()-lower_bound(ve[i].begin(),ve[i].end(),s)+1+pre[s-1]+suf[ve[i][j]+1]); 31 } 32 } 33 } 34 else{ 35 int p=i>>1; 36 for(int j=0;j<ve[i].size();++j){ 37 if(ve[i][j]<p){ 38 int s=p+(p-ve[i][j]); 39 ans=max(ans,upper_bound(ve[i].begin(),ve[i].end(),s)-ve[i].begin()-j+pre[ve[i][j]-1]+suf[s+1]); 40 } 41 if(ve[i][j]>p){ 42 int s=p-(ve[i][j]-p); 43 ans=max(ans,j+ve[i].begin()-lower_bound(ve[i].begin(),ve[i].end(),s)+1+pre[s-1]+suf[ve[i][j]+1]); 44 } 45 } 46 } 47 } 48 printf("%d\n",ans); 49 return 0; 50 }
B. 走格子
考试时这个题错误理解了题意,30分钟打了一个模拟,
得了25分,改了之后变成了85分。
对两种情况建边:
1.暴力走向旁边的四个格子,边权为1
2.向四个方向的墙开一枪,走向最近的墙以到达子弹打到的墙,边权为由当前点到最近墙的距离。
显然开枪的目的是到达子弹打到的位置,而开枪不需要时间,所以这个算法是没有问题的。
跑最短路即可。
至于如何处理出每个点向它最近的墙的距离。
将每个墙同时入队,bfs即可。