2019-7-2 杂题选讲
T1 Lynyrd Skynyrd
以前做过,预处理好从第 i i i位开始跳,需要跳完一个循环后落到了什么位置,把边界设置好 i n f inf inf就能倍增开跳,然后再把这个预处理数组做一个后缀取最小值,每次询问只需要 O ( 1 ) O(1) O(1)判断是否 g [ l ] < = r g[l]<=r g[l]<=r即可
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int LOGN=20;
int n,m,q,p[N],a[N],l,r,tot;
int nxt[N],pos[N],f[N][LOGN],g[N];
int er[N];
int ans[N];
int read()
{
int x=0,f=1;
char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return f*x;
}
int main()
{
er[0]=1;
for (int i=1;i<LOGN;i++) er[i]=er[i-1]*2;
n=read(),m=read(),q=read();
for (int i=1;i<=n;i++) p[i]=read();
for (int i=1;i<=m;i++) a[i]=read();
for (int i=1;i<=n;i++) nxt[p[i]]=p[i%n+1];
for (int i=1;i<=n;i++) pos[i]=m+1;
for (int i=0;i<N;i++)
for (int j=0;j<LOGN;j++) f[i][j]=m+1;
for (int i=m;i>=1;i--)
{
f[i][0]=pos[nxt[a[i]]];
for (int j=1;j<LOGN;j++) f[i][j]=f[f[i][j-1]][j-1];
pos[a[i]]=i;
}
g[m+1]=m+1;
for (int i=m;i>=1;i--)
{
int l=i,rest=n-1;
while (rest)
{
int jump=log2(rest);
l=f[l][jump];
rest-=er[jump];
}
g[i]=min(g[i+1],l);
}
while (q--)
{
l=read(),r=read();
if (g[l]<=r) ans[++ans[0]]=1;
else ans[++ans[0]]=0;
}
for (int i=1;i<=ans[0];i++) printf("%d",ans[i]);
return 0;
}
好题T2 Tree Generator™
回归树的括号序列的定义:向深层递归是左括号
(
(
(,向上层回溯是右括号
)
)
)
那么就可以发现:
1,形如
(
(
(
(
(
)
)
)
)
)
((((()))))
((((()))))的一段括号序列的意义是,从某个点向深层走并原路返回
2,形如
)
)
)
)
)
(
(
(
(
(
)))))(((((
)))))(((((的一段括号序列的意义是,从某个点向上回溯至某个位置并向下搜索另一个子树,这样走出来的是一条路径
那我们需要的直径明显是形如2的一条最长路径,但又因为中间可能遍历了其他子树,但是中间这些括号都能对消掉,于是我们可以想到直径在括号序列上的意义是选择一段括号序列,将可以匹配对消的去掉后的最长长度
这个东西好像可以在线段树上合并,考虑一下怎么合并
记录一下信息:
l
n
,
r
n
,
l
x
,
r
x
,
s
u
m
ln,rn,lx,rx,sum
ln,rn,lx,rx,sum分别表示区间前后缀最小最大和以及区间和
a
n
s
,
l
a
n
s
,
r
a
n
s
ans,lans,rans
ans,lans,rans分别表示区间答案,区间中必须取左端点的最优答案,必须取右端点的最优答案
最后的难点就是合并信息
拿lans举个例子:
t
[
p
]
.
l
a
n
s
=
m
a
x
(
t
[
l
s
]
.
l
a
n
s
,
m
a
x
(
−
t
[
l
s
]
.
l
n
+
t
[
l
s
]
.
r
x
+
t
[
r
s
]
.
l
x
,
−
t
[
l
s
]
.
s
u
m
+
t
[
r
s
]
.
l
a
n
s
)
)
;
t[p].lans=max(t[ls].lans,max(-t[ls].ln+t[ls].rx+t[rs].lx,-t[ls].sum+t[rs].lans));
t[p].lans=max(t[ls].lans,max(−t[ls].ln+t[ls].rx+t[rs].lx,−t[ls].sum+t[rs].lans));
第一项是继承左儿子答案
第二项是左区间左边有一段))左区间右端和有区间左端有一段((
第三项是强制去左区间并继承右儿子的答案
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,q,tot;
int ans[N],tpp=0;
char s[N];
struct data
{
int sum,ln,lx,rn,rx,ans,lans,rans;
}t[N<<3];
void Pushup(int p,int ls,int rs)
{
t[p].sum=t[ls].sum+t[rs].sum;
t[p].ln=min(t[ls].ln,t[ls].sum+t[rs].ln);
t[p].lx=max(t[ls].lx,t[ls].sum+t[rs].lx);
t[p].rn=min(t[rs].rn,t[rs].sum+t[ls].rn);
t[p].rx=max(t[rs].rx,t[rs].sum+t[ls].rx);
t[p].lans=max(t[ls].lans,max(-t[ls].ln+t[ls].rx+t[rs].lx,-t[ls].sum+t[rs].lans));
t[p].rans=max(t[rs].rans,max(t[rs].rx-t[rs].ln-t[ls].rn,t[rs].sum+t[ls].rans));
t[p].ans=max(max(t[ls].ans,t[rs].ans),max(t[ls].rans+t[rs].lx,t[rs].lans-t[ls].rn));
return;
}
void Build(int p,int l,int r)
{
if (l==r)
{
int val=(s[l]=='(')?1:-1;
t[p]=(data){val,min(0,val),max(0,val),min(0,val),max(0,val),1,1,1};
return;
}
int ls=p<<1,rs=p<<1|1,mid=(l+r)>>1;
Build(ls,l,mid);
Build(rs,mid+1,r);
Pushup(p,ls,rs);
return;
}
void Modify(int p,int l,int r,int k,int v)
{
if (l==r)
{
t[p]=(data){v,min(v,0),max(v,0),min(v,0),max(v,0),1,1,1};
return;
}
int ls=p<<1,rs=p<<1|1,mid=(l+r)>>1;
if (k<=mid) Modify(ls,l,mid,k,v);
else Modify(rs,mid+1,r,k,v);
Pushup(p,ls,rs);
return;
}
int main()
{
scanf("%d%d",&n,&q);
tot=(n-1)<<1;
scanf("%s",s+1);
Build(1,1,tot);
ans[++tpp]=t[1].ans;
while (q--)
{
int l,r;
scanf("%d%d",&l,&r);
Modify(1,1,tot,l,(s[r]=='(')?1:-1);
Modify(1,1,tot,r,(s[l]=='(')?1:-1);
swap(s[l],s[r]);
ans[++tpp]=t[1].ans;
}
for (int i=1;i<=tpp;i++) printf("%d\n",ans[i]);
return 0;
}
T3 Three Religions
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为三个小串分别匹配到 i , j , k i,j,k i,j,k位需要的最短的大串前缀,加入一个字符多填一些状态,删掉一个字符撤回一些状态,所有边界设置 n + 1 n+1 n+1
d p [ i ] [ j ] [ k ] = m i n ( d p [ n x t [ d p [ i − 1 [ j ] [ k ] ] ] . . . ) dp[i][j][k]=min(dp[nxt[dp[i-1[j][k]]]...) dp[i][j][k]=min(dp[nxt[dp[i−1[j][k]]]...)
#include<bits/stdc++.h>
using namespace std;
const int N=260;
const int S=1e5+5;
const int inf=1e9+7;
int n,q,tmp;
int nxt[S][N],pos[30],dp[N][N][N];
int s1[N],s2[N],s3[N],l1=0,l2=0,l3=0;
char s[S],opt[10],ch[10];
void Init()
{
for (int i=0;i<N;i++)
for (int j=0;j<N;j++)
for (int k=0;k<N;k++) dp[i][j][k]=n+1;
for (int i=0;i<26;i++) pos[i]=n+1;
for (int i=0;i<=n+1;i++)
for (int j=0;j<26;j++) nxt[i][j]=n+1;
dp[0][0][0]=0;
for (int i=n;i>=0;i--)
{
for (int j=0;j<26;j++) nxt[i][j]=pos[j];
pos[s[i]-'a']=i;
}
return;
}
void DO(int i,int j,int k)
{
if (i>0) dp[i][j][k]=min(dp[i][j][k],nxt[dp[i-1][j][k]][s1[i]]);
if (j>0) dp[i][j][k]=min(dp[i][j][k],nxt[dp[i][j-1][k]][s2[j]]);
if (k>0) dp[i][j][k]=min(dp[i][j][k],nxt[dp[i][j][k-1]][s3[k]]);
// printf("dp[%d][%d][%d] = %d\n",i,j,k,dp[i][j][k]);
return;
}
int main()
{
scanf("%d%d",&n,&q);
scanf("%s",s+1);
Init();
while (q--)
{
scanf("%s",opt+1);
scanf("%d",&tmp);
switch (opt[1])
{
case '+':
scanf("%s",ch+1);
if (tmp==1)
{
s1[++l1]=ch[1]-'a';
int i=l1;
for (int j=0;j<=l2;j++)
for (int k=0;k<=l3;k++) DO(i,j,k);
}
else if (tmp==2)
{
s2[++l2]=ch[1]-'a';
int j=l2;
for (int i=0;i<=l1;i++)
for (int k=0;k<=l3;k++) DO(i,j,k);
}
else
{
s3[++l3]=ch[1]-'a';
int k=l3;
for (int i=0;i<=l1;i++)
for (int j=0;j<=l2;j++) DO(i,j,k);
}
break;
case '-':
if (tmp==1)
{
int i=l1;
for (int j=0;j<=l2;j++)
for (int k=0;k<=l3;k++) dp[i][j][k]=n+1;
l1--;
}
else if (tmp==2)
{
int j=l2;
for (int i=0;i<=l1;i++)
for (int k=0;k<=l3;k++) dp[i][j][k]=n+1;
l2--;
}
else
{
int k=l3;
for (int i=0;i<=l1;i++)
for (int j=0;j<=l2;j++) dp[i][j][k]=n+1;
l3--;
}
break;
}
if (dp[l1][l2][l3]<=n) printf("YES\n");
else printf("NO\n");
}
/* for (int i=0;i<=n;i++)
{
for (int j=0;j<=5;j++) printf("%d ",nxt[i][j]);
printf("\n");
}*/
return 0;
}
本文深入解析了三道算法竞赛题目,包括跳转路径优化、树的括号序列直径求解及三种宗教字符串匹配问题。介绍了如何利用预处理、倍增、后缀取最小值、线段树合并等高级算法技巧解决复杂问题。
2051

被折叠的 条评论
为什么被折叠?



