比赛传送门
作者: fn
题目大意
给定一个长为
n
n
n 的序列,求有多少个区间包括序列中所有种类的数字。
考察内容
双指针
分析
枚举左端点
𝐿
𝐿
L ,合法的右端点集合一定是区间
[
𝑅
,
𝑛
]
[𝑅,𝑛]
[R,n] ,且
𝑅
𝑅
R 随着
𝐿
𝐿
L 的递增而不减。
采用双指针,在移动指针的同时维护区间内每种数字的个数以及出现数字的种类数,包含全部种类则累加答案。
两个指针都只枚举一遍,时间复杂度 𝑂 ( 𝑛 ) 𝑂(𝑛) O(n) 。
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+10;
ll n,m,a[N];
int vis[N];
int num=0;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
ll ans=0;
ll p=1,q=0;
while(p<=n-1 || q<=n-1){
while(num<m && q<=n-1){
q++;
vis[a[q]]++;
if(vis[a[q]]==1){
num++;
}
}
// num>=m || q>=n
if(num>=m){ //
ans+=n-q+1;
}
while(num>=m){
vis[a[p]]--;
if(vis[a[p]]==0){
num--;
}
p++;
if(num>=m){
ans+=n-q+1;
}
}
if(q==n && num<m)break;
}
cout<<ans<<endl;
return 0;
}
/*
5 3
1 1 1 2 3
*/
题目大意
河道里有
𝑛
𝑛
n 个荷叶排成一排,从第
𝑖
(
<
𝑛
)
𝑖(<𝑛)
i(<n) 个荷叶出发可以跳到第
[
𝑖
+
1
,
𝑖
+
𝑎
𝑖
]
[𝑖+1,𝑖+𝑎_𝑖]
[i+1,i+ai] 个荷叶上,有两只青蛙从第1个荷叶出发,每一步都独立地等概率随机地跳向后边的荷叶,求两只青蛙以相同步数到达第
𝑛
𝑛
n 个荷叶的概率。
𝑛 ≤ 8000 𝑛≤8000 n≤8000 ,保证 1 ≤ 𝑎 𝑖 ≤ 𝑛 − 𝑖 1≤𝑎_𝑖≤𝑛−𝑖 1≤ai≤n−i 。
考察内容
差分,概率dp
分析
概率dp。
状态:
a
[
i
]
[
j
]
a[i][j]
a[i][j] :跳
i
i
i 次到第
j
j
j 格的概率。
边界:
a
[
0
]
[
1
]
=
1
a[0][1]=1
a[0][1]=1
跳了0次,必定在第1格。
转移:
枚举往后跳了
j
j
j 次。
枚举当前跳到第
i
i
i 格。
更新区间
a
[
j
]
[
i
+
1
,
i
+
2
,
.
.
.
,
i
+
a
i
]
a[j][i+1,i+2,...,i+a_i]
a[j][i+1,i+2,...,i+ai] 。
一些细节:
时间复杂度
用树状数组维护带log,亲测tle了。
用差分维护,时间复杂度
𝑂
(
𝑛
2
)
𝑂(𝑛^2)
O(n2) ,可以通过。注意一下枚举的顺序。
空间复杂度
a
[
i
]
[
j
]
a[i][j]
a[i][j]必须用int,亲测用long long会内存超限。
#include
#define ll long long
// #define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=8002;
const ll mod=998244353;
ll read(ll &n){
char ch=' '; ll q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
n=q*w; return n;
}
ll power(ll a,ll b){ // a^b%mod
ll c=1;
for(;b;b>>=1){
if(b&1)c=c*a%mod;
a=a*a%mod;
}
return c;
}
int a[N][N]; // a[i][j]: 跳i次到第j格的概率
int c[N][N]; // 第二维是a的差分数组
ll n;
ll a0[N]; // 最多跳几格
ll p[N]; // 1/a0[i]
signed main(){ // AC
read(n);
for(int i=1;i<=n-1;i++){
read(a0[i]);
p[i]=power(a0[i],mod-2)%mod; // 概率为 1/a0[i]
}
a[0][1]=1; // 跳了0次,必定在第1格
c[0][1]=1;
c[0][2]=mod-1; // 差分
// 差分,复杂度O(n^2)
for(int j=1;j<=n;j++){ // 枚举往后跳了j次
ll cnt=0;
for(int i=1;i<=n;i++){ // 求出a[j-1][i]
cnt+=c[j-1][i];
cnt%=mod;
a[j-1][i]=cnt;
}
for(int i=1;i<=n-1;i++){ // 枚举当前的格子,当前第i格
ll t1=a[j-1][i]*p[i]%mod; //
if(t1!=0){ // 对区间a[j][i+1]-a[j][i+a0[i]]进行更新,O(1)
c[j][i+1]+=t1;
c[j][i+1]%=mod;
c[j][i+a0[i]+1] += mod-t1;
c[j][i+a0[i]+1] %= mod;
}
}
}
for(int i=1;i<=n;i++){
ll cnt=0;
for(int j=0;j<=n;j++){
cnt+=c[i][j];
cnt%=mod;
}
a[i][n]=cnt;
}
ll ans=0;
for(int i=0;i<=n;i++){
ll t0=a[i][n]%mod; // 单点查询
ans+=t0*t0%mod;
ans%=mod;
}
cout<<(ans+mod)%mod<<endl;
return 0;
}
/*
5
1 1 1 1
*/
题目大意
给定
𝑘
𝑘
k 个字符串
𝑆
1
,
𝑆
2
,
.
.
.
,
𝑆
𝑘
𝑆_1,𝑆_2,...,𝑆_𝑘
S1,S2,...,Sk ,求有多少个不同的公共回文子串。
𝑘
≤
5
,
∑
𝑖
=
1
𝑘
∣
𝑆
𝑖
∣
≤
3
×
1
0
5
𝑘≤5,∑_{𝑖=1}^𝑘 |𝑆_𝑖 |≤3×10^5
k≤5,∑i=1k∣Si∣≤3×105
考察内容
回文树
分析
对每个字符串
𝑆
𝑆
S 分别求出所有本质不同回文子串。
使用回文树求解,时间复杂度
𝑂
(
∑
∣
𝑆
∣
)
𝑂(∑|𝑆|)
O(∑∣S∣)。
#include
using namespace std;
#define ll long long
#define MN 300010
#define SN 35
char c[MN];int b[MN];
int l[MN],fl[MN],n,tot,Lt,tr[SN][MN],v[MN],g[MN];
inline void init(){
l[1]=-1;Lt=tot=fl[0]=1;c[0]='a'+26;
}
inline ll Max(ll a,ll b){
return a<b?b:a;
}
inline void add(int x,int y){
int cc=c[x]-'a';
while(c[x]!=c[x-l[Lt]-1])Lt=fl[Lt];
if(!tr[cc][Lt]){
l[++tot]=l[Lt]+2;int g=fl[Lt];
while(c[x-l[g]-1]!=c[x])g=fl[g];
fl[tot]=tr[cc][g];tr[cc][Lt]=tot;
}g[Lt=tr[cc][Lt]]|=y;
}
int main(){ // 回文树
int k;
scanf("%d",&k);init();
int n=0;
for(int I=0;I<k;++I){
scanf("%s",c+n+1);
for(int i=n+1;c[i]!='\0';++i)b[i]=I;
n=strlen(c+1);c[++n]='z'+I+1;b[n]=k;
}
ll res=0;
for(int i=1;i<=n;++i)add(i,1<<b[i]);
for(int i=tot;i;--i)g[fl[i]]|=g[i];
for(int i=tot;i;--i)if(g[i]==(1<<k)-1)++res;
cout<<res;
return 0;
}