题意:
给一长度为n的正整数序列,对于一个$[l,r]$,如果存在$d ∈ [l,r]$ 满足 $a(d)|a(i) (i∈[l, r])$ 则称之为合法子串。
找出所有的最长合法子串。
解法:
1. $O(n sum_{i=1}^n { au ( a(i) ) })$ 做法,枚举a序列中出现的数字d,将 i*d 所在的位置标上1,
然后从所有d所在的位置左右暴力拓展。
注意到只有下一位为1时才会拓展,所以暴力拓展的复杂度和标1的复杂度相同。
考虑每个数字a(i),最多被访问了它的约数次,所以总复杂度 $O(n sum_{i=1}^n { au ( a(i) ) })$
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <vector> 5 #include <algorithm> 6 7 #define N 300010 8 #define M 1000010 9 10 using namespace std; 11 12 int n,m,tot,ans,a[N],a0[N]; 13 bool v[N],del[N]; 14 vector<int> pos[M],ansv; 15 16 int main() 17 { 18 // freopen("test.txt","r",stdin); 19 while(~scanf("%d",&n)) 20 { 21 for(int i=1;i<=n;i++) del[i]=0; 22 ansv.clear(); 23 ans=m=1; 24 for(int i=1;i<=n;i++) 25 { 26 scanf("%d",&a[i]); 27 m = max(m, a[i]); 28 pos[a[i]].push_back(i); 29 a0[i]=a[i]; 30 } 31 sort(a0+1,a0+n+1); 32 tot=1; 33 for(int i=2;i<=n;i++) 34 if(a0[i]!=a0[i-1]) a0[++tot]=a0[i]; 35 for(int d=1;d<=tot;d++) 36 { 37 int x=a0[d]; 38 // cout <<"tet "<< x << endl; 39 for(int i=x;i<=m;i+=x) 40 { 41 int nl=pos[i].size(); 42 for(int j=0;j<nl;j++) v[pos[i][j]]=1; 43 } 44 int len=pos[x].size(); 45 // for(int i=1;i<=n;i++) cout<<v[i]; 46 // cout<<endl; 47 for(int i=0;i<len;i++) 48 { 49 int tmp=pos[x][i]; 50 if(del[tmp]) continue; 51 del[tmp]=1; 52 int l=tmp,r=tmp; 53 while(l>1 && v[l-1]) 54 { 55 l--; 56 if(a[l] == x) del[l]=1; 57 } 58 while(r<n && v[r+1]) 59 { 60 r++; 61 if(a[r] == x) del[r]=1; 62 } 63 if(r-l+1>ans) 64 { 65 ans=r-l+1; 66 ansv.clear(); 67 ansv.push_back(l); 68 } 69 else if(r-l+1==ans) 70 ansv.push_back(l); 71 } 72 for(int i=x;i<=m;i+=x) 73 { 74 int nl=pos[i].size(); 75 for(int j=0;j<nl;j++) v[pos[i][j]]=0; 76 } 77 } 78 for(int i=1;i<=m;i++) pos[i].clear(); 79 int len=ansv.size(); 80 cout << len << ' ' << ans-1 << endl; 81 sort(ansv.begin(), ansv.end()); 82 for(int i=0;i<len;i++) cout << ansv[i] << ' '; 83 cout << endl; 84 } 85 return 0; 86 }
2.考虑优化上一个做法,将a(i)按照一定的顺序枚举,用类似对a(i)做筛法的方式,由a(i)推到i*a(i),这样均摊复杂度大概是
$O(nloglogn)$
3.仔细观察,从小到大枚举a(i),对于每一个a(i)暴力向左向右拓展,并记录相应位置是否被拓展到过,
对于当前枚举到的值为 a(i) 的位置
(1)如果其向左 / 向右拓展到了标记过的位置,那一定无法再次拓展。
(2)如果相应位置已经被访问过,那么从当前位置拓展,答案一定会变差。
这样,我们只要枚举a(i),暴力拓展即可。
每个位置只会被访问一次 且 每个数字被访问一次,总复杂度 $O(m)$ (懒得加桶排了)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <vector> 5 #include <algorithm> 6 7 #define N 300010 8 #define M 1000010 9 10 using namespace std; 11 12 int n,m,tot,ans,a[N],a0[N]; 13 bool del[N]; 14 vector<int> pos[M],ansv; 15 16 int main() 17 { 18 // freopen("test.txt","r",stdin); 19 while(~scanf("%d",&n)) 20 { 21 for(int i=1;i<=n;i++) del[i]=0; 22 ansv.clear(); 23 ans=m=1; 24 for(int i=1;i<=n;i++) 25 { 26 scanf("%d",&a[i]); 27 m = max(m, a[i]); 28 pos[a[i]].push_back(i); 29 a0[i]=a[i]; 30 } 31 sort(a0+1,a0+n+1); 32 tot=1; 33 for(int i=2;i<=n;i++) 34 if(a0[i]!=a0[i-1]) a0[++tot]=a0[i]; 35 for(int d=1;d<=tot;d++) 36 { 37 int x=a0[d]; 38 int len=pos[x].size(); 39 for(int i=0;i<len;i++) 40 { 41 int tmp=pos[x][i]; 42 if(del[tmp]) continue; 43 del[tmp]=1; 44 int l=tmp,r=tmp; 45 while(l>1 && a[l-1]%x==0) del[--l]=1; 46 while(r<n && a[r+1]%x==0) del[++r]=1; 47 if(r-l+1>ans) 48 { 49 ans=r-l+1; 50 ansv.clear(); 51 ansv.push_back(l); 52 } 53 else if(r-l+1==ans) 54 ansv.push_back(l); 55 } 56 } 57 for(int i=1;i<=m;i++) pos[i].clear(); 58 int len=ansv.size(); 59 cout << len << ' ' << ans-1 << endl; 60 sort(ansv.begin(), ansv.end()); 61 for(int i=0;i<len;i++) cout << ansv[i] << ' '; 62 cout << endl; 63 } 64 return 0; 65 }