1. 程式人生 > >【noip模擬賽】數字對

【noip模擬賽】數字對

好題一道,二分的單調性很隱祕,還介紹了ST演算法的拓展

【題目描述】

小H是個善於思考的學生,現在她又在思考一個有關序列的問題。

她的面前浮現出一個長度為n的序列{ai},她想找出一段區間[L, R](1 <= L <= R <= n)。

這個特殊區間滿足,存在一個k(L <= k <= R),並且對於任意的i(L <= i <= R),ai都能被ak整除。這樣的一個特殊區間 [L, R]價值為R - L。

小H想知道序列中所有特殊區間的最大價值是多少,而有多少個這樣的區間呢?這些區間又分別是哪些呢?你能幫助她吧。

【輸入格式】

第一行,一個整數n.

第二行,n個整數,代表ai.

【輸出格式】

第一行兩個整數,num和val,表示價值最大的特殊區間的個數以及最大價值。

第二行num個整數,按升序輸出每個價值最大的特殊區間的L.

【樣例輸入1】

5

4 6 9 3 6

【樣例輸出1】

1 3

2

【樣例輸入2】

5

2 3 5 7 11

【樣例輸出2】

5 0

1 2 3 4 5

【資料範圍】

30%: 1 <= n <= 30 , 1 <= ai <= 32.

60%: 1 <= n <= 3000 , 1 <= ai <= 1024.

80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.

100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.

題解

30% :暴力列舉判斷。O(n^4) or O(n^3)。

60% :特殊區間的特點實際上就是區間最小值等於這個區間的GCD,於是暴力或遞推算出每個區間最小值與GCD。而對於最大價值,可以通過二分來進行求解。複雜度O(n ^ 2)。

100%:在60%的基礎上,最小值與GCD都使用RMQ演算法來求解,對於這道題推薦使用ST表,線段樹會被艹飛。複雜度O(nlogn)。

實際上此題資料水機智的暴力踩標程


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 2147483647
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int bin[20];
int n,top,res;
int a[500005],L[500005],q[500005],ans[500005];
int mn[500005][19],g[500005][19];
int gcd(int x,int y)
{
	return y==0?x:gcd(y,x%y);
}
void pre()
{
	for(int i=1;i<=n;i++)
		mn[i][0]=g[i][0]=a[i];
	for(int j=1;j<=18;j++)
		for(int i=1;i<=n;i++)
		{
			if(i+bin[j]-1<=n)
			{
				mn[i][j]=min(mn[i][j-1],mn[i+bin[j-1]][j-1]);
				g[i][j]=gcd(g[i][j-1],g[i+bin[j-1]][j-1]);
			}
			else break;
		}
}
bool query(int l,int r)
{
	int t=L[r-l+1],R=r-bin[t]+1;
	return min(mn[l][t],mn[R][t])==gcd(g[l][t],g[R][t]);
}
void solve(int x)
{
	for(int i=1;i<=n;i++)
		if(i+x-1<=n)
			if(query(i,i+x-1))
				ans[++top]=i;
}
int main()
{
	//freopen("pair.in","r",stdin);
	//freopen("pair.out","w",stdout);
	bin[0]=1;for(int i=1;i<20;i++)bin[i]=bin[i-1]<<1;
	L[0]=-1;for(int i=1;i<=500000;i++)L[i]=L[i>>1]+1;
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	pre();
	int l=1,r=500000;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		top=0;
		solve(mid);
		if(top)res=mid,l=mid+1;
		else r=mid-1;
	}
	top=0;
	solve(res);
	printf("%d %d\n",top,res-1);
	for(int i=1;i<=top;i++)
		printf("%d ",ans[i]);
	return 0;
}