P3498 [POI2010]KOR-Beads 題解
阿新 • • 發佈:2021-01-07
【思路分析】
都標著 \(Hash\) 的標籤,所以一般要用 \(Hash\) 做瞎編中;
此方法極其暴力,但是至少我 \(AC\) 了。
總體思路 :
可以求這 \(n\) 個數的前後綴 \(Hash\)值,之後從每 \(1\) 個分為一段到每 \(n - 1\) 個分為一段,依次進行判斷。
- 因為題目中說 : 子串都是可以反轉的 ,所以首先求前後綴 \(Hash\) 值,字尾 \(Hash\) 值就是把字首 \(Hash\) 值的求法反過來。
【Code】
void prepare()//前後綴Hash值和進位制。 { po[0] = 1; for(int i = 1;i <= n;i ++) {//進位制 po[i] = po[i - 1] * p; } for(int i = 1;i <= n;i ++) { Hash_z[i] = Hash_z[i - 1] * p + s[i];//字首Hash值 } for(int i = n;i >= 1;i --) { Hash_f[i] = Hash_f[i + 1] * p + s[i];//字尾Hash值 } return; }
-
之後就開始進行對每次分割的計算,對每次的結果進行比較和儲存,得出答案,但是,我試了一下,發現 \(TLE\) 了兩個點 第七個和第九個 (其實吸氧也能 \(AC\) )。
-
於是開始了我們的剪枝之路,發現了兩個規率 :
- 因為存的是正反兩個子串,所以再判重的時候只需判斷字首 \(Hash\) 是否相等即可,優化 \(0.1\) 秒。
- 對於每一個求出的答案,如果在後面的列舉中可分的段數還比已知答案要小,肯定不是最優解,所以直接排除即可,優化 \(0.69\) 秒。
之後我也想不出什麼可以優化的方法了,吸了口小氧就過去了,不吸氧的情況下是 \(1.5\) 秒,吸了氧是 \(339ms\)
【Code】
#include<cstdio> #include<cmath> #include<iostream> #include<cstring> #include<queue> #include<algorithm> #include<stdlib.h> #include<time.h> #include<map> #include<vector> #include<set> #define ull unsigned long long #define ll long long #define M 1000010 #define N 1010 #define qaq cout<<"可行QAQ"<<endl #define INF 0x3f3f3f3f using namespace std; const int mod1 = 19260817; const int mod2 = 19660813; const ull mod3 = 212370440130137957ll; const int p = 10007; /*================================================*/ int n; int s[M]; ull Hash_z[M], Hash_f[M], po[M];;//正Hash值 ,反Hash值,進位制 int cnt;//計算有幾個最優解 int ans_cnt;//看可分為幾段不同的值 vector<ull> qp;//儲存每次分割的答案 int maxn = -1e5;//計算最終的可分的最大的段數 vector<int> ans;//儲存每個最優解 int m; //剪枝用 /*================================================*/ inline int read() { int s = 0, f = 0;char ch = getchar(); while (!isdigit(ch)) f |= ch == '-', ch = getchar(); while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar(); return f ? -s : s; } void prepare()//前後綴Hash 值和進位制位 { po[0] = 1; for(int i = 1;i <= n;i ++) { po[i] = po[i - 1] * p; } for(int i = 1;i <= n;i ++) { Hash_z[i] = Hash_z[i - 1] * p + s[i]; } for(int i = n;i >= 1;i --) { Hash_f[i] = Hash_f[i + 1] * p + s[i]; } return; } bool check(int x)//判斷函式,判斷是否有這個子串 { for(int i = 0;i <qp.size(); i ++) { if(qp[i] == x) return false; } return true; } void solve(int k) { ans_cnt = 0; for(int i = k;i <= n;i += k) { int sum_z = Hash_z[i] - Hash_z[i - k] * po[k]; //這個子串字首Hash值 int sum_f = Hash_f[i - k + 1] - Hash_f[i + 1] * po[k];//字尾 if(check(sum_z)) {//判斷 qp.push_back(sum_z); ans_cnt++;//累加可分的段數 //if(check(sum_f)) //剪枝 1 ,效果如上 qp.push_back(sum_f); } } if(maxn == ans_cnt) {//當已知的最優解和新求出的解相同時 ans.push_back(k);//記錄k 值 cnt++;//最優解個數++ } else if(maxn < ans_cnt) {//又新求出更優的解 ans.clear();//清空原來的答案序列 ans.push_back(k);//新增解 maxn = ans_cnt; cnt = 1; } if(ans_cnt != 0) { m = min(m,n / ans_cnt);//剪枝 2 效果如上 } qp.clear();//多次分割記得清空 } /*=================================================*/ signed main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n = read(); for(int i = 1;i <= n;i ++) { s[i] = read(); } prepare(); m = n; for(int i = 1;i <= m;i ++) solve(i); printf("%d %d\n",maxn,cnt); for(int i = 0;i <ans.size();i ++) printf("%d ",ans[i]);//輸出 return 0; }
小錯誤
取底數的時候要取個大點的值(或者寫雙 \(Hash\)),利用 \(unsigned\) \(long\) \(long\) 的自然溢位來判斷,否則很容易和我一樣,用 \(31\) 來做進位制,使得 \(WA\) 聲一片。