題目大意:
給你一個長度爲n的序列,要你求這個序列中的最長國王子序列長度,如果長度小於n/2,輸出-1,否則輸出長度。
國王序列定義:對於一個序列a1,a2,a3,.....,an。當且僅當有一個整數q(1<=q<p)使得,且 q*ai-1 = ai (mod p)。
題解:
觀察一下時間複雜度,如果用類似LIS的做法,時間複雜度肯定不行。
但是考慮到這裏長度是和n/2比較,因此通過這個長度的限制,我們發現如果長度大於等於n/2,
那麼這個子序列的數在原序列的最小間隔小於等於2。
解法一:推結論
因此我們就可以直接先求出原序列相鄰的數以及相隔一個數的公比,用unordered_map存就行了。
然後我們可以發現如果一個公比的出現次數大於某個定值,那麼以這個公比形成的序列就可能滿足條件。
經估計出現次數大於n/8(大概是這個值,具體證明參照ecFinal)的公比形成的序列是滿足條件的。
我們對這些公比形成的序列求出長度,並維護最大值就行了。
代碼實現:
#pragma GCC optimize(2) #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<cstdio> #include<cstdlib> #include<vector> #include<map> #include<set> #include<stack> #include<queue> #include<unordered_map> #define PI atan(1.0)*4 #define E 2.718281828 #define rp(i,s,t) for (register int i = (s); i <= (t); i++) #define RP(i,t,s) for (register int i = (t); i >= (s); i--) #define ll long long #define ull unsigned long long #define mst(a,b) memset(a,b,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define pii pair<int,int> #define pb push_back #define debug printf("ac\n"); using namespace std; inline int read() { int a=0,b=1; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') b=-1; c=getchar(); } while(c>='0'&&c<='9') { a=(a<<3)+(a<<1)+c-'0'; c=getchar(); } return a*b; } const int INF = 0x3f3f3f3f; const int N = 2e5+7; ll b[N],p; unordered_map<ll,int> mp; int n,T; ll quick_mod(ll a,ll b){ ll res=1; while(b){ if(b&1) res=(res*a)%p; b>>=1; a=(a*a)%p; } return res%p; } ll inv(ll x) {return quick_mod(x,p-2);} int calc(ll q){ unordered_map<ll,int> dp; int res=0; RP(i,n,1){ dp[b[i]]=max(dp[b[i]],dp[b[i]*q%p]+1); res=max(res,dp[b[i]]); } return res; } int main(){ T=read(); while(T--){ mp.clear(); scanf("%d%lld",&n,&p); rp(i,1,n) scanf("%lld",&b[i]); rp(i,2,n){ ll q=b[i]*inv(b[i-1])%p; mp[q]++; } rp(i,3,n){ ll q=b[i]*inv(b[i-2])%p; mp[q]++; } int ans=0; for(auto it:mp) if(it.second>=n/8) ans=max(ans,calc(it.first)); if(ans*2<n) printf("-1\n"); else printf("%d\n",ans); } return 0; } /* 10 10 1 2 1 2 4 2 4 5 3 5 8 4 8 10 5 1 3 1 3 6 2 6 7 3 7 9 4 9 10 5 */
解法二:隨機化算法
我們可以在序列中隨機取兩個數,那麼這兩個數出現在序列中的可能性大於1/4( (1/2)*(1/2) = (1/4) ).
當取得次數爲x時,出現在答案的可能性就變成了 1 - (3/4)^x。
(因爲每次不出現的概率爲(3/4),x次都不出現就是(3/4)^x,那麼出現至少一次的概率就是1-(3/4)^x)。
因此我們可以儘可能多次的隨機取兩個數,即x儘可能地大,這樣正確性的概率就越大。
當然這兩個數也不能就隨機地取,這樣可能導致公比相差很大。
而根據我們開頭推出來的規律,如果我們取的數是答案中相鄰的數,那麼它們的最小距離小於等於2。
這樣我們就可以隨機取兩個間隔小於等於2的數,以這兩個數爲公比,求出序列長度,迭代多次維護最大序列長度就行了。
trick:注意不能直接使用rand()函數,因爲這個函數的範圍比較小(0-32767),不符合題意,需要用到一個C++11的隨機數生成函數(範圍在[-maxint,maxint]之間)。
代碼實現:
#pragma GCC optimize(2) #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<cstdio> #include<cstdlib> #include<vector> #include<map> #include<set> #include<stack> #include<queue> #include<random> #include <chrono> #include<bits/stdc++.h> #define PI atan(1.0)*4 #define E 2.718281828 #define rp(i,s,t) for (register int i = (s); i <= (t); i++) #define RP(i,t,s) for (register int i = (t); i >= (s); i--) #define ll long long #define ull unsigned long long #define mst(a,b) memset(a,b,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define pii pair<int,int> #define mp make_pair #define pb push_back #define debug printf("ac\n"); using namespace std; inline int read() { int a=0,b=1; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') b=-1; c=getchar(); } while(c>='0'&&c<='9') { a=(a<<3)+(a<<1)+c-'0'; c=getchar(); } return a*b; } const int INF = 0x3f3f3f3f; const int N = 2e5+7; ll inv[N],p,b[N]; int n; ll quick_mod(ll a,ll b){ ll res=1; while(b){ if(b&1) res=(res*a)%p; b>>=1; a=(a*a)%p; } return res%p; } ll solve(ll x,ll y){ ll q=b[y]*inv[x]%p; ll ans=2; int pre=x; RP(i,x-1,1){ if(b[pre]*inv[i]%p==q){ ans++; pre=i; } } pre=y; rp(i,y+1,n){ if(b[i]*inv[pre]%p==q){ ans++; pre=i; } } return ans; } int main(){ int T=read(); // srand(time(0)); mt19937 mt_rand(time(0)); while(T--){ scanf("%d%lld",&n,&p); rp(i,1,n) scanf("%lld",&b[i]); rp(i,1,n) inv[i]=quick_mod(b[i],p-2)%p; ll ans=-1; rp(i,1,150){ int num=mt_rand()%(n-1)+1; rp(j,num+1,num+2) ans=max(ans,solve(num,min(j,n))); } if(ans>=(n+1)/2) printf("%lld\n",ans); else printf("-1\n"); } return 0; }
參考鏈接: