洛谷P3498 [POI2010] KOR-Beads 题解
P3498 [POI2010] KOR-Beads
对于数列 ${A_i}$ ,求 $k$ 使数列被分为 $\lfloor \frac{n}{k}\rfloor$ 个部分后不同子数列种类最多(子串翻转前后为一类子串)
-
很容易想到:枚举 $k\ \in\ [1,n]$ ,记录每个 $k$ 下不同种类数,然后取最优即可,那么问题来了:如何计算种类数?
-
暴戾算法:
一种纯粹的暴戾算法(雾)便是:对于每个 $k$ 使用
map
与string
记录当前串是否出现过,但问题在于字符串需要不断改变,即需要 $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
。 -