洛谷P3498 [POI2010] KOR-Beads 題解

CheemsaDoge發表於2024-03-23

P3498 [POI2010] KOR-Beads

對於數列 ${A_i}$ ,求 $k$ 使數列被分為 $\lfloor \frac{n}{k}\rfloor$ 個部分後不同子數列種類最多(子串翻轉前後為一類子串)

  • 很容易想到:列舉 $k\ \in\ [1,n]$ ,記錄每個 $k$ 下不同種類數,然後取最優即可,那麼問題來了:如何計算種類數?

    • 暴戾演算法:

      一種純粹的暴戾演算法(霧)便是:對於每個 $k$ 使用 mapstring 記錄當前串是否出現過,但問題在於字串需要不斷改變,即需要 $2e5$ 的 string 變數,無論是時間複雜度還是空間複雜度都是不可接受的。

    • 正解雜湊:

      因此我們直接使用 hash 演算法,並選擇 map 或者 set 判重。

      /*code by CheemsaDoge*/
      #include <bits/stdc++.h>//用了萬能頭,我就是嘯紂老師口中的低水平(哭
      template<typename T> inline void read(T &x) {/*...*/}//fast input
      template<typename T> inline void write(T x){/*...*/}
      const int MAXM=1e6+1145;const int MAXN=200000+11145;const int INF=2147483647ll;//2^31-1
      #define pc putchar('\n')
      #define pk putchar(' ')
      #define ull unsigned long long
      /*---------------------------------pre------------------------------------*/
      std::set<ull>hash;//直接用unsigned long long讓他自己溢位來
      const ull sp=19260817;
      ull s1[MAXN],s2[MAXN],pp[MAXN];
      int a[MAXN],n;
      ull get1(int l,int r) {return s1[r]-s1[l-1]*pp[r-l+1];} //獲得hash值(從前往後計算
      ull get2(int l,int r) {return s2[l]-s2[r+1]*pp[r-l+1];} //獲得hash值(從後往前計算
      template <typename T> T min(const T _a,const T _b) {return _a<_b?_a:_b;} //手寫 min 函式
      int get_ans(int k) {
      	hash.clear();
      	for(int i=0;i*k+k<=n;i++) hash.insert(min<ull>(get1(i*k+1,i*k+k),get2(i*k+1,i*k+k))); //插入當前字串(子串)的hash值
      	return hash.size(); //返回種類數(set不允許出現相同元素,所以前面可以安心插
      }//得到答案
      std::vector<int>ans;//動態陣列儲存滿足條件的k
      int maxn=-1;//最大的種類數
      int main(){
      	read(n);
      	for(int i=1;i<=n;i++) read(a[i]);
      	pp[0]=1;
      	for(int i=1;i<=n;i++) {
      		s1[i]=1ull*a[i]+s1[i-1]*sp;
      		pp[i]=pp[i-1]*sp;
      	}
      	for(int i=n;i>=1;i--) s2[i]=a[i]+s2[i+1]*sp;//初始化hash
      	for(int i=1;i<=n;i++) {
      		int ma=get_ans(i);//獲得k的種類數
      		if(maxn<ma) { //更新答案
      			ans.clear();
      			ans.push_back(i);
      			maxn=ma;
      		}
      		else if(maxn==ma) ans.push_back(i);
      	}
      	write(maxn);pk;write(ans.size());pc;//輸出(pc是putchar('\n'),前面的宏有定義
      	for(int i=0;i<(int)ans.size();i++) write(ans[i]),pk;//pk是putchar(' ')
      	return 0; //好習慣捏
      }
      

    關於模數:這道題如果 sp 被設為 10007 親測會掛,機房裡的 $dalao$ 就有這麼掛的。

    所以一個好的模數是必不可少的,建議用 13331 或者某人的生日 19260817

相關文章