總結
T1 各種做法迷亂了我的心智,帶上推 KMP 的半小時總共調了 2.5 h T1,浪費時間太長了,導致最後 T2、T4 暴力都沒打,打了就 Rank 4 了
中間看題 +【資料刪除】 用了半小時
T2 簡單推了二十多分鐘覺得沒前途先開了 T3,一眼會思路,感覺有點小細節,決定先打暴力再打倍增最佳化,結果 \(n^2\) 一打就打了一小時,離比賽結束就剩 5 分鐘了,於是直接擺了
賽後改完 T3 的倍增:我去還好賽時沒打出來,要是打出來了就全 MLE 了
明天一定打暴力!!!
A.選取字串
KMP、字串好題
因為所有字串都是大字串的字首,所以一旦我們每個字串的字首字尾的長度確定了,那麼字首字尾長什麼樣也就確定了
設 \(f_i\) 為所有相同字首字尾長度可以為 \(i\) 的字串的個數
我們列舉 \(i\in [1,n]\),每次欽定兩個串 \(p、q\) 裡必須有一個是 \(S_i\),而另一個串可以在合法的範圍內任意選。
設 \(g_i\) 表示必選字首串 \(S_i\) 時另一個串可以選的字串的個數
那麼答案就是 \(\sum_{i=1}^n \binom {f_i}{k}\times (2\times g_i-1)\) + \(\binom {n+1}{k}\)
Why:另一個串有 \(g_i\) 種可能,而 \(p、q\) 的順序不同為不同的方案,所以乘 2,再減去 \(p、q\) 都選 \(S_i\) 的情況(這個情況 \(p、q\) 順序不同也是一種方案)
最後 \(p、q\) 全選空串的方案單獨算即為 \(n+1\) 個串裡選 \(k\) 個
現在考慮 \(f_i、g_i\) 的計算:
-
對於 \(f_i\):KMP 求出所有字串 \(S_{[1,i]}\) 的 包的er 值 \(pi_i\),即最長的既是字首也是字尾的長度,那麼每個字串 \(S_i\) 相同字首字尾可以為 \(S_{[1,\ pi_i]}\),同時也可以為 \(S_{[1,\ pi_i]}\) 的字首字尾,所以從大到小遞推計算:
f[pi[i]] += f[i];
-
對於 \(g_i\):顯然選 \(S_i\) 時同樣可以為所有字串的字首字尾的只能是 \(S_i\) 的字首字尾,也是同理遞推得到,
g[i] += g[pi[i]];
code
#include<bits/stdc++.h>
#define int long long
#define Aqrfre(x, y) freopen(#x ".in", "r", stdin),freopen(#y ".out", "w", stdout)
#define mp make_pair
#define Type int
#define qr(x) x=read()
typedef long long ll;
using namespace std;
inline Type read(){
char c=getchar(); Type x=0, f=1;
while(!isdigit(c)) (c=='-'?f=-1:f=1), c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48), c=getchar();
return x*f;
}
const int N = 1e6 + 5;
const int mod = 998244353;
inline vector<int> pretix(string s){
int len = s.length();
vector<int>v(len+10); v[0] = 0;
for(int i=1; i<len; i++){
int j = v[i-1]; //v: The length of the string which from 1 to i - 1
while((i == j or s[i] != s[j]) and j) j = v[j-1];
if(s[i] == s[j]) j++;
v[i] = j;
}
return v;
}
string s;
int n, k, sum[N], f[N], g[N];
vector<int>pi;
int fact[N], inv[N], fainv[N];
inline void AqrPre(){
fact[0] = fact[1] = 1;
inv[0] = inv[1] = 1;
fainv[0] = fainv[1] = 1;
for(int i=2; i<=n+3; i++){
fact[i] = 1ll * fact[i-1] * i % mod;
inv[i] = 1ll * inv[mod%i] * (mod - mod / i) % mod;
fainv[i] = 1ll * fainv[i-1] * inv[i] % mod;
}
}
inline int C(int x, int y){
return x < y ? 0 : 1ll * fact[x] * fainv[y] % mod * fainv[x-y] % mod;
}
/*
思路應該是顯然的??實現也簡單啊只不過我假了一版又一版
那這題確實是簡單簽到了啊,但我打了兩個半小時??
還有救麼??這讓我怎麼翻?後面三題只打暴力肯定不行吧
*/
signed main(){ // string
Aqrfre(string, string);
qr(k); cin>>s; n = s.length();
AqrPre(); pi = pretix(s);
for(int i=0; i<n; i++) f[pi[i]]++, g[i+1] = 1;
for(int i=1; i<=n; i++) g[i] += g[pi[i-1]];
for(int i=n; i>=1; i--)
if(i and pi[i-1]) f[pi[i-1]] += f[i];
int ans = 0;
for(int i=1; i<=n; i++){
ans += C(f[i]+1, k) * ((g[i] + 1) * 2 - 1) % mod;
ans %= mod;
}
cout<<(ans+C(n+1, k))%mod<<"\n";
return 0;
}
B.取石子
博弈論
搞不懂放博弈論的目的是什麼,仙姑
C.均衡區間
二維數點
感覺思路比 T1 還簡單啊
啥是二維數點啊我不會??掛 \(\log\) 是沒前途的,所以提供一種不掛 \(\log\) 的目前最優解( 極限 \(n^2\) 的哈哈
以 \(i\) 為左端點時舉例:
設 \(Rmax_i\) 為右側第一個大於 \(a_i\) 的位置,\(Rmin_i\) 同理
預處理出所有的 \(Rmax_i\) 以及 \(Rmin_i\),和 \(Ranum_i\) 表示從 \(i\) 開始遞增子序列的長度,\(Rbnum_i\) 表示 \(i\) 開始遞減子序列的長度
可以根據轉移理解一下 \(Ranum,Rbnum\) 陣列:Ranum[i] = Ranum[Rmax[i]] + 1;
發現從 \(max(Rmax, Rmin)\) 開始到最後,除了所有的比當前最大的還大的、比當前最小的還小的,其餘都可以作為合法的右端點
統計答案:
我們把 \(Rmax_i、Rmin_i\) 裡較小的一個不斷跳到大於另一個的位置
如 \(Rmax_i<Rmin_i\) 時,不斷賦值 \(r1 = Rmax_{r1}\ 直到\ r1 >= Rmin_i\)
這個時候答案就是 \((n-Rmin_i+1)-Ranum[Rmin_i]-Rbnum[r1]-2\)
code
#include<bits/stdc++.h>
#define Aqrfre(x, y) freopen(#x ".in", "r", stdin),freopen(#y ".out", "w", stdout)
#define mp make_pair
#define Type int
#define qr(x) x=read()
typedef long long ll;
using namespace std;
inline Type read(){
char c=getchar(); Type x=0, f=1;
while(!isdigit(c)) (c=='-'?f=-1:f=1), c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48), c=getchar();
return x*f;
}
const int N = 1e6 + 5;
const int mod = 998244353;
/*
怎麼 T3 也放一眼題??
以 i 為左端點舉例:
預處理 i 右側第一個比它大、小的數 Rmax[i], Rmin[i]
i+1 ~ min(Rmax[i], Rmin[i])-1 合法,假設是 Rmin 小一點
現在的 Rmin 變為 Rmin[Rmin[i]]
錯了錯了,反了!!
直接數卡後的比當前最大還大 ~,比當前最小還小 ~ 就做完了
丸辣!成小丑了,n^2 暴力調了一個小時,嗚嗚 ST 表胡不上了
還剩 3 分鐘,擺了,拿多少分看命吧
*/
int n, a[N], id, L[N], R[N];
int Rmax[N], Rmin[N], Ranum[N], Rbnum[N];
int Lmax[N], Lmin[N], Lanum[N], Lbnum[N];
deque<int>qmax, qmin;
signed main(){ // interval
Aqrfre(interval, interval);
qr(n); qr(id);
for(int i=1; i<=n; i++) qr(a[i]);
for(int i=1; i<=n; i++){
while(qmax.size() and a[qmax.front()] <= a[i]) Rmax[qmax.front()] = i, qmax.pop_front();
while(qmin.size() and a[qmin.front()] >= a[i]) Rmin[qmin.front()] = i, qmin.pop_front();
qmax.push_front(i); qmin.push_front(i);
}
while(qmax.size()) qmax.pop_front();
while(qmin.size()) qmin.pop_front();
for(int i=n; i>=1; i--){
while(qmax.size() and a[qmax.front()] <= a[i]) Lmax[qmax.front()] = i, qmax.pop_front();
while(qmin.size() and a[qmin.front()] >= a[i]) Lmin[qmin.front()] = i, qmin.pop_front();
qmax.push_front(i); qmin.push_front(i);
}
for(int i=1; i<=n; i++){
if(!Rmax[i]) Rmax[i] = n + 1;
if(!Rmin[i]) Rmin[i] = n + 1;
if(!Lmax[i]) Lmax[i] = 0;
if(!Lmin[i]) Lmin[i] = 0;
}
Ranum[n+1] = Rbnum[n+1] = -1;
for(int i=n; i>=1; i--){
Ranum[i] = Ranum[Rmax[i]] + 1;
Rbnum[i] = Rbnum[Rmin[i]] + 1;
int r1 = Rmax[i], r2 = Rmin[i], cnt = 0;
if(!Ranum[i] or !Rbnum[i]){
R[i] = 0; continue;
}
if(r1 < r2){
if(id == 2){ R[i] = 0; continue; }
while(r1 < r2) r1 = Rmax[r1];
cnt = Ranum[r1] + 2 + Rbnum[r2];
cnt = n - r2 + 1 - cnt;
}
else{
if(id == 2){ R[i] = 0; continue; }
while(r2 < r1) r2 = Rmin[r2];
cnt = Ranum[r1] + 2 + Rbnum[r2];
cnt = n - r1 + 1 - cnt;
}
R[i] = cnt;
}
for(int i=1; i<=n; i++) cout<<R[i]<<" "; cout<<"\n";
Lanum[0] = Lbnum[0] = -1;
for(int i=1; i<=n; i++){
Lanum[i] = Lanum[Lmax[i]] + 1;
Lbnum[i] = Lbnum[Lmin[i]] + 1;
int l1 = Lmax[i], l2 = Lmin[i], cnt = 0;
if(!Lanum[i] or !Lbnum[i]){
L[i] = 0; continue;
}
if(l1 > l2){
if(id == 2){ L[i] = 0; continue; }
while(l1 > l2) l1 = Lmax[l1];
cnt = Lanum[l1] + 2 + Lbnum[l2];
cnt = l2 - cnt;
}
else{
if(id == 2){ L[i] = 0; continue; }
while(l2 > l1) l2 = Lmin[l2];
cnt = Lanum[l1] + 2 + Lbnum[l2];
cnt = l1 - cnt;
}
L[i] = cnt;
}
for(int i=1; i<=n; i++) cout<<L[i]<<" ";
return 0;
}