洛谷P3498 [POI2010] KOR-Beads 题解

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

posted @ 2024-03-23 20:25  CheemsaDoge  阅读(9)  评论(0编辑  收藏  举报