10.12T1 寻找性质
Description
Input
第二行有N个数,第i个数表示旋转之前第i个位置的牌的编号。
Output
Sample Input
Sample Output
Hint
30%的数据满足:N ≤ 500;
60%的数据满足:N ≤ 5000;
100%的数据满足:1 ≤ N ≤ 100 000。
算法一:对于30%的数据: 直接枚举区间直接模拟,时间复杂度O(N3)。
算法二:对于60%的数据:枚举旋转中心点,然后再枚举旋转的端点, 我们可以用O(n)的预处理求前缀和记录固定点,总时间复杂度O(N2)。
算法三:对于100%的数据:假设有最优解为[i,j](i,j皆为下标,A[i],A[j]才是题目所要输出的答案)。if(A[i]!=j&&A[j]!=i),就是A[i]和A[j]经过旋转之后都没有成为不动点,那么[i+1,j-1]也是一个最优解(如果i+1>j-1,那么[1,1]也是最优解)。两端旋转之后如果都没成为不动点我们可以去掉两端,不停地去掉后得到最优解为[x,y]则有A[x]=y或者A[y]=x,或者x=y=1( [1,1]可以不参与讨论).
因此最优解一定是[min(i,A[i]),max(i,A[i])](i=1,2,3......,n)。
只需要枚举n个值就能找到最优解,由此找最优解问题变成了查询问题。假设i<=A[i],要查询的区间变成了三段[1,i-1] [i, A[i] ] [ A[i+1] ,n],固定点个数O(1)的时间查询。中间这一段 [i, A[i] ]经过了一次旋转,对于旋转后的固定点j有A[j]+j=A[i]+i且,而[j,A[j]]也是我们要考虑成为最优解的一个旋转,我们事先将这n种旋转按照轴心不同分类,每一类由旋转区间长度短到长排序。以上算法因为要排序所以复杂度为O(N log N)。
简明一点就是[i,a[i]]一定是我们的答案区间,然后查询前后的区间没有翻转的个数可以前缀和水掉,至于中间的我们在读入的时候可以vector记录一下i+a[i]这个值有多少个位置是它,然后在枚举的时候就可以二分vector里面的位置就可以了(我tm强行多带了一个n导致50分,人傻自带一个n)
code:
1 #include<iostream> 2 #include<cstdio> 3 #include<vector> 4 #include<algorithm> 5 #define N 1000005 6 using namespace std; 7 int a[N],s[N]; 8 vector<int>check[1000005]; 9 int main(){ 10 int n; 11 cin>>n; 12 for(int i=1;i<=n;i++){ 13 cin>>a[i]; 14 s[i]=s[i-1]+(a[i]==i?1:0); 15 check[a[i]+i].push_back(i); 16 } 17 int max0=1; 18 if(s[n]==n){ 19 cout<<n; 20 return 0; 21 } 22 for(int i=1;i<=n;i++){ 23 if(a[i]==i)continue; 24 int temp=0; 25 temp+=s[min(i,a[i])-1]+s[n]-s[max(i,a[i])]; 26 int now=a[i]+i; 27 int l=lower_bound(check[now].begin(),check[now].end(),min(i,a[i]))-check[now].begin(); 28 int r=upper_bound(check[now].begin(),check[now].end(),max(i,a[i]))-check[now].begin(); 29 max0=max(max0,r-l+temp); 30 } 31 cout<<max0; 32 return 0; 33 }
over