T1:词典
有一个好猜的结论:对于一个字符串,若它当中的最小字符大于等于某其他字符串中的最大字符,那么这个字符串一定不可行。
证明也很简单,若最小字符大于最大字符,显然一定不可行。若最小字符等于最大字符,由于字符串长度相同,且字符串两两不同,所以次小的一定大于另一个次大的,依然不可行,由此得证。代码及其简单,就不贴了。
T2:三值逻辑
见到这个题第一眼就可以想到并查集了。对于 true 和 false,我们可以将它们视为正负关系,这样就极大简化操作了。初始化常量,令 T = − F , U = 0 T=-F,U=0 T=−F,U=0,然后我们处理完所有赋值操作后,再处理答案。
考虑一个数 x x x,在并查集中,若 x x x 祖先为 U U U,那么答案自然加 1 1 1;若它的祖先有 − x -x −x,说明是非关系起冲突了,那么 x x x 只能为 U U U。由此,我们处理的重点便是如何判断 x x x 是否存在这两个祖先。
若 x = T / F / U x=T/F/U x=T/F/U,可以直接判断;若之前访问过 − x -x −x,那么也可以判断。然后考虑 x x x 大于或小于 0 0 0 的情况,注意处理数组越界的情况,就可以了。代码不长,但细节还是比较多的。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
const int N=3e5+10,T=2e5+10,F=-T,U=0;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int c,t,n,m,fa[N],vis[N];
int find(int x){
if(x==T||x==F) return x;
if(x==U||vis[-x+n]) return U;
if(vis[x+n]) return T;
int res;
if(x<0){
if(x==-fa[-x]) return x;//为根节点
vis[x+n]=1,res=find(-fa[-x]),vis[x+n]=0;
}
else{
if(x==fa[x]) return x;
vis[x+n]=1,res=fa[x]=find(fa[x]),vis[x+n]=0;
}
return res;
}
int main(){
c=rd,t=rd;
while(t--){
n=rd,m=rd;memset(fa,0,sizeof(fa)),memset(vis,0,sizeof(is));
for(int i=1;i<=n;i++) fa[i]=i;
while(m--){
char op;cin>>op;int x=rd,y;
if(op=='T') fa[x]=T;
else if(op=='F') fa[x]=F;
else if(op=='U') fa[x]=U;
else if(op=='+') y=rd,fa[x]=fa[y];
else y=rd,fa[x]=-fa[y];
}
int ans=0;for(int i=1;i<=n;i++){
//cout<<find(i)<<":::"<<endl;
if(find(i)==U) ans++;
}
printf("%d\n",ans);
}
return 0;
}
T3:双序列拓展
感觉比 T4 难。
神仙思维题,依然是由特殊性质启发正解的题。
我们先看两个序列满足的条件,实际上就是对于任意位置 i i i,要么 f i > g i f_i>g_i fi>gi,要么 f i < g i f_i<g_i fi<gi,且这种大小关系只能满足一个。我们可以规定 f f f 恒小于 g g g,两种情况处理方法几乎是一样的。
先看 Subtask 1~7,可以用比较暴力的 O ( n 2 ) O(n^2) O(n2) 做法。 f i , g j f_i,g_j fi,gj 可以分别由 ( X i − 1 , Y j ) , ( X i , Y j − 1 ) , ( X i − 1 , Y j − 1 ) (X_{i-1},Y_j),(X_i,Y_{j-1}),(X_{i-1},Y_{j-1}) (Xi−1,Yj),(Xi,Yj−1),(Xi−1,Yj−1) 三种情况拓展过来。然后暴力枚举 i , j i,j i,j 即可,时间复杂度 O ( T n m ) O(Tnm) O(Tnm)。
暴力的 dp 做法似乎没有什么优化的余地了,对正解也没什么启发,但我们将其抽象为一个网格图,发现了什么?
我们实际上就是从左上角的
(
1
,
1
)
(1,1)
(1,1),每次可以往右、往下、往右下移动,最终只要能到右下角的
(
n
,
m
)
(n,m)
(n,m) 即为成功。而设
(
x
,
y
)
(x,y)
(x,y) 分别表示
X
,
Y
X,Y
X,Y 的下标,我们能到这个格子,当且仅当
X
x
<
Y
y
X_x<Y_y
Xx<Yy。
这样的转化看似影响不大,但我们再去看 Subtask 8~14,由于其特殊性质,发现只要
X
m
i
n
<
Y
m
i
n
,
X
m
a
x
<
Y
m
a
x
X_{min}<Y_{min},X_{max}<Y_{max}
Xmin<Ymin,Xmax<Ymax,那么这个网格就会像这样:
其中红格都是我们能到的地方,如果不理解的话一定要手动画图去体会一下,这是最关键的部分。
所以我们就可以通过找最大最小值位置,来判断是否合法并不断缩小范围了,最终只要 x = 1 x=1 x=1 或 y = 1 y=1 y=1,我们就可以构造出 f f f 和 g g g 了。
但是若没有这个性质呢?实际上我们发现,改变的只有
X
X
X 和
Y
Y
Y 的最值所在位置,所以网格图就会变为这种:
这个时候我们只要能从
(
1
,
1
)
(1,1)
(1,1) 到红格,并从红格到
(
n
,
m
)
(n,m)
(n,m) 即可。相比之前,我们需要多判断右下角。
至此,我们解决了这个问题。总结一下思路:预处理出 X X X 和 Y Y Y 的前缀及后缀最值所在位置,然后两个 check 函数分别判断左上角和右下角的可行情况,不断缩小边界。
代码并不长,大多都是在预处理最值位置,关键部分就是两个 check 函数。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
const int N=5e5+10;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int c,n,m,T,a[N],b[N],ta[N],tb[N],f[N],g[N];
struct node{int mnpos,mxpos;}prex[N],prey[N],sufx[N],sufy[N];
bool check1(int x,int y,int n,int m){
if(x==1||y==1) return true;
node A=prex[x-1],B=prey[y-1];
if(f[A.mnpos]<g[B.mnpos]) return check1(A.mnpos,y,n,m);
if(f[A.mxpos]<g[B.mxpos]) return check1(x,B.mxpos,n,m);
return false;
}
bool check2(int x,int y,int n,int m){
if(x==n||y==m) return true;
node A=sufx[x+1],B=sufy[y+1];
if(f[A.mnpos]<g[B.mnpos]) return check2(A.mnpos,y,n,m);
if(f[A.mxpos]<g[B.mxpos]) return check2(x,B.mxpos,n,m);
return false;
}
bool solve(int aa[],int bb[],int n,int m){
if(aa[1]>=bb[1]) return false;
memcpy(f,aa,sizeof(f)),memcpy(g,bb,sizeof(g));
for(int i=1;i<=n;i++){
if(i==1) prex[i]={1,1};
else{
prex[i].mnpos=prex[i-1].mnpos,prex[i].mxpos=prex[i-1].mxpos;
if(f[i]<f[prex[i].mnpos]) prex[i].mnpos=i;
if(f[i]>f[prex[i].mxpos]) prex[i].mxpos=i;
}
}
for(int i=1;i<=m;i++){
if(i==1) prey[i]={1,1};
else{
prey[i].mnpos=prey[i-1].mnpos,prey[i].mxpos=prey[i-1].mxpos;
if(g[i]<g[prey[i].mnpos]) prey[i].mnpos=i;
if(g[i]>g[prey[i].mxpos]) prey[i].mxpos=i;
}
}
for(int i=n;i>=1;i--){
if(i==n) sufx[i]={n,n};
else{
sufx[i].mnpos=sufx[i+1].mnpos,sufx[i].mxpos=sufx[i+1].mxpos;
if(f[i]<f[sufx[i].mnpos]) sufx[i].mnpos=i;
if(f[i]>f[sufx[i].mxpos]) sufx[i].mxpos=i;
}
}
for(int i=m;i>=1;i--){
if(i==m) sufy[i]={m,m};
else{
sufy[i].mnpos=sufy[i+1].mnpos,sufy[i].mxpos=sufy[i+1].mxpos;
if(g[i]<g[sufy[i].mnpos]) sufy[i].mnpos=i;
if(g[i]>g[sufy[i].mxpos]) sufy[i].mxpos=i;
}
}
node A=prex[n],B=prey[m];
if(f[A.mnpos]>=g[B.mnpos]||f[A.mxpos]>=g[B.mxpos]) return false;
return check1(A.mnpos,B.mxpos,n,m)&&check2(A.mnpos,B.mxpos,n,m);
}
int main(){
c=rd,n=rd,m=rd,T=rd;
for(int i=1;i<=n;i++) a[i]=rd;
for(int i=1;i<=m;i++) b[i]=rd;
if(solve(a,b,n,m)||solve(b,a,m,n)) cout<<1;
else cout<<0;
while(T--){
int kx=rd,ky=rd;
memcpy(ta,a,sizeof(a)),memcpy(tb,b,sizeof(b));
for(int i=1;i<=kx;i++){int x=rd,y=rd;ta[x]=y;}
for(int i=1;i<=ky;i++){int x=rd,y=rd;tb[x]=y;}
if(solve(ta,tb,n,m)||solve(tb,ta,m,n)) cout<<1;
else cout<<0;
}
return 0;
}
T4:天天爱打卡
比较典的数据结构优化 dp。
先设状态,
f
i
f_i
fi 表示前
i
i
i 天,第
i
i
i 天必跑步,最终能量最大值。我们枚举上次从哪一天开始跑,转移方程不难得出:
f
i
=
max
{
max
t
=
0
j
−
1
f
t
−
(
j
−
i
)
×
d
+
∑
[
l
p
,
r
p
]
∈
[
j
,
i
]
v
p
}
f_i=\max \left \{ \max_{t=0}^{j-1}f_t-(j-i)\times d+\sum_{[l_p,r_p]\in [j,i]}v_p \right \}
fi=max{maxt=0j−1ft−(j−i)×d+∑[lp,rp]∈[j,i]vp}。
设
g
i
=
max
j
=
0
i
f
j
g_i=\max_{j=0}^{i} f_j
gi=maxj=0ifj,我们就有了
O
(
n
2
)
O(n^2)
O(n2) 的做法。发现这样的式子可以运用线段树优化,具体地:线段树叶子结点代表下标,维护相应位置最优值。对于
i
→
i
+
1
i\to i+1
i→i+1,
[
0
,
i
]
[0,i]
[0,i] 中的所有位置值都会减
d
d
d,然后对于区间右端点等于
i
+
1
i+1
i+1 的区间,设左端点为
l
l
l,则
[
0
,
l
]
[0,l]
[0,l] 中的值都会增加相应的
v
v
v。做完修改后,由于我们不能跑超过连续
k
k
k 天,所以我们查询
[
i
−
k
+
1
,
i
+
1
]
[i-k+1,i+1]
[i−k+1,i+1] 中的最大值。此时最大值会改变,再令
i
+
2
i+2
i+2 位置的值加上此时的答案。
此时时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),而 n n n 最大可达 1 0 9 10^9 109,考虑如何优化。发现 m m m 的数据范围很小,所以真正有用的位置并不多,只有一个区间的 l − 1 , r l-1,r l−1,r 两个位置。将这些位置存下来进行离散化,再进行上述操作,这道题就做完了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
const int N=2e5+10;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int c,T,n,m,k,d,lsh[N],tot,mx[N<<2],tag[N<<2];
struct node{int l,r,v;}a[N];
bool cmp(node a,node b){return a.r<b.r;}
void pushdown(int u){
if(!tag[u]) return;
tag[u<<1]+=tag[u],tag[u<<1|1]+=tag[u];
mx[u<<1]+=tag[u],mx[u<<1|1]+=tag[u];
tag[u]=0;
}
void pushup(int u){mx[u]=max(mx[u<<1],mx[u<<1|1]);}
void modify(int u,int l,int r,int ql,int qr,int v){
if(ql<=l&&r<=qr){mx[u]+=v,tag[u]+=v;return;}
pushdown(u);int mid=(l+r)>>1;
if(ql<=mid) modify(u<<1,l,mid,ql,qr,v);
if(qr>mid) modify(u<<1|1,mid+1,r,ql,qr,v);
pushup(u);
}
int query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return mx[u];
pushdown(u);int mid=(l+r)>>1,ans=0;
if(ql<=mid) ans=max(ans,query(u<<1,l,mid,ql,qr));
if(qr>mid) ans=max(ans,query(u<<1|1,mid+1,r,ql,qr));
return ans;
}
signed main(){
c=rd,T=rd;
while(T--){
n=rd,m=rd,k=rd,d=rd;tot=0;memset(mx,0,sizeof(mx)),memset(tag,0,sizeof(tag));
for(int i=1;i<=m;i++){int x=rd,y=rd,v=rd;a[i]={x-y,x,v};}
for(int i=1;i<=m;i++) lsh[++tot]=a[i].l,lsh[++tot]=a[i].r;
sort(lsh+1,lsh+1+tot),tot=unique(lsh+1,lsh+1+tot)-lsh-1;
for(int i=1;i<=m;i++) a[i].l=lower_bound(lsh+1,lsh+1+tot,a[i].l)-lsh,a[i].r=lower_bound(lsh+1,lsh+1+tot,a[i].r)-lsh;
int ans=0,p=1;sort(a+1,a+1+m,cmp);
for(int i=1;i<=tot;i++){
modify(1,0,tot-1,0,i-1,-d*(lsh[i]-lsh[i-1]));
while(p<=m&&a[p].r==i) modify(1,0,tot-1,0,a[p].l,a[p].v),p++;
int t=lower_bound(lsh+1,lsh+1+tot,lsh[i]-k)-lsh;
ans=max(ans,query(1,0,tot-1,t,i-1));
if(i+1<tot) modify(1,0,tot-1,i+1,i+1,ans);
}
printf("%lld\n",ans);
}
return 0;
}