CSPS2022 题解
T1
容易想到枚举 \(B,C\),然后 \(A,D\) 可以预处理,即对于 \(i\) 处理存在路径 \(1\rightarrow j\rightarrow i\) 中 \(j\) 的权值最大的,那么只需枚举 \(B,C\) 然后分别取最大的 \(A,D\)。但是因为此时最大的 \(A\) 可能与 \(C,D\) 重复,所以需要保存前三大的 \(j\) 才能保证找到最大的合法路径。为了避免分讨,可以枚举前三大的组合,判断是否合法并更新 \(\max\) 即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(u) for(int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
#define mkp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
#define in read()
inline int read(){
int p=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))p=p*10+c-48,c=getchar();
return p*f;
}
const int N=2505,M=10005,inf=1000000000000000000;
int n,m,k;
int val[N];
struct edge{int v,nxt;}e[M<<1];
int head[N],en;
inline void insert(int u,int v){e[++en]={v,head[u]},head[u]=en;}
int dis[N][N],vis[N];
priority_queue<pii>q;
inline void dij(int s){
for(int i=1;i<=n;i++)dis[s][i]=1000000000,vis[i]=0;
dis[s][s]=0;q.push(mkp(0,s));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;vis[u]=1;
FOR(u)if(dis[s][v]>dis[s][u]+1)
dis[s][v]=dis[s][u]+1,q.push({-dis[s][v],v});
}
}
struct node{
int d,f;
friend bool operator>=(node a,node b){
return a.d>=b.d;
}
}mx[3][N];
int ans;
signed main(){
n=in,m=in,k=in;
for(int i=2;i<=n;i++)val[i]=in;
for(int i=1,u,v;i<=m;i++)u=in,v=in,insert(u,v),insert(v,u);
for(int i=1;i<=n;i++)dij(i),mx[0][i].d=mx[1][i].d=mx[2][i].d=-inf;
for(int i=2;i<=n;i++)if(dis[1][i]<=k+1){
node tmp={val[i],i};
for(int j=2;j<=n;j++)if(i^j&&dis[i][j]<=k+1){
if(tmp>=mx[0][j])mx[2][j]=mx[1][j],mx[1][j]=mx[0][j],mx[0][j]=tmp;
else if(tmp>=mx[1][j])mx[2][j]=mx[1][j],mx[1][j]=tmp;
else if(tmp>=mx[2][j])mx[2][j]=tmp;
}
}
for(int i=2,sum;i<=n;i++){
for(int j=2;j<=n;j++)if(i^j&&dis[i][j]<=k+1){
sum=val[i]+val[j];
for(int l=0;l<3;l++){
for(int r=0;r<3;r++){
if(mx[l][i].f==0||mx[r][j].f==0)continue;
if(mx[l][i].f!=mx[r][j].f&&mx[l][i].f!=j&&i!=mx[r][j].f)
ans=max(ans,sum+mx[l][i].d+mx[r][j].d);
}
}
}
}cout<<ans;
return 0;
}
T2
注意到两人选择的只可能是正负的 \(\max,\min\) 或是 \(0\),为了避免分讨,直接对每个数组开五个 \(ST\) 然后暴力找到这些数,然后 \(5*5\) 的枚举选的方法即可,实现比较优美,开一个 \(ST\) 的结构体,再开一个数组的结构体。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(u) for(int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
#define mkp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
#define in read()
inline int read(){
int p=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))p=p*10+c-48,c=getchar();
return p*f;
}
const int NM=100005,inf=1000000000000000000;
int n,m,q,lg2[NM];
inline void init(){
for(int i=2;i<=100000;i++)lg2[i]=lg2[i/2]+1;
}
struct ST{
int len,maxn[20][NM],minn[20][NM];
ST(){for(int i=1;i<=100000;i++)maxn[0][i]=-inf,minn[0][i]=inf;}
inline void pro(){
for(int i=1;i<=19;i++)
for(int j=1;j+(1<<(i-1))<=n;j++)
maxn[i][j]=max(maxn[i-1][j],maxn[i-1][j+(1<<(i-1))]),
minn[i][j]=min(minn[i-1][j],minn[i-1][j+(1<<(i-1))]);
}
inline int queryMax(int l,int r){
int t=lg2[r-l+1];
return max(maxn[t][l],maxn[t][r-(1<<t)+1]);
}
inline int queryMin(int l,int r){
int t=lg2[r-l+1];
return min(minn[t][l],minn[t][r-(1<<t)+1]);
}
};
struct ar{
int len,a[NM];
ST P,N,Z;
inline void init(){
for(int i=1;i<=len;i++){
if(a[i]>0)P.maxn[0][i]=P.minn[0][i]=a[i];
else if(a[i]<0)N.maxn[0][i]=N.minn[0][i]=a[i];
else Z.maxn[0][i]=Z.minn[0][i]=a[i];
}
P.pro(),N.pro(),Z.pro();
}
}A,B;
inline int solve(int x,int al,int ar){
if(x==0)return 0;
int tmp,ans=inf;
tmp=B.N.queryMax(al,ar);
if(tmp!=-inf)ans=min(ans,x*tmp);
tmp=B.N.queryMin(al,ar);
if(tmp!=inf)ans=min(ans,x*tmp);
tmp=B.P.queryMax(al,ar);
if(tmp!=-inf)ans=min(ans,x*tmp);
tmp=B.P.queryMin(al,ar);
if(tmp!=inf)ans=min(ans,x*tmp);
tmp=B.Z.queryMax(al,ar);
if(tmp!=-inf)ans=min(ans,x*tmp);
return ans;
}
signed main(){
n=in,m=in,q=in,init();
A.len=n,B.len=m;
for(int i=1;i<=n;i++)A.a[i]=in;
for(int i=1;i<=m;i++)B.a[i]=in;
A.init(),B.init();
for(int i=1,al,ar,bl,br,tmp,ans;i<=q;i++){
al=in,ar=in,bl=in,br=in,ans=-inf;
tmp=A.N.queryMax(al,ar);
if(tmp!=-inf)ans=max(ans,solve(tmp,bl,br));
tmp=A.N.queryMin(al,ar);
if(tmp!=inf)ans=max(ans,solve(tmp,bl,br));
tmp=A.P.queryMax(al,ar);
if(tmp!=-inf)ans=max(ans,solve(tmp,bl,br));
tmp=A.P.queryMin(al,ar);
if(tmp!=inf)ans=max(ans,solve(tmp,bl,br));
tmp=A.Z.queryMax(al,ar);
if(tmp!=-inf)ans=max(ans,solve(tmp,bl,br));
cout<<ans<<'\n';
}
return 0;
}
T3
删加一条边或某个点的所有入边,判断当前图是否是内向基环树森林。
只需要判断每个点的出度是否都是 \(1\),那么由于每个点的出度都是 \(1\),就有一种随机化 hash 的做法。
具体来说,每个点有一个随机权值 \(a\),每个点在当前图中的权值 \(b\) 是所有入边的点的 \(a\) 之和。那么如果所有点 \(b\) 的和等于所有点 \(a\) 的和,就有较大概率当前图满足条件。
可以在 OI-wiki 学习随机化技巧。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(i,u,G) for(int i=G.head[u],v=G.e[i].v;i;i=G.e[i].nxt,v=G.e[i].v)
#define mkp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
#define in read()
inline int read(){
int p=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))p=p*10+c-48,c=getchar();
return p*f;
}
const int N=500005;
int n,m,Q;
struct edge{int u,v,nxt;};
struct Gra{
edge e[N];
int head[N],en;
inline void insert(int u,int v){e[++en]={u,v,head[u]},head[u]=en;}
}rG;
int a[N],b[N],suma[N],sum,now;
int t[N],tu[N],tv[N],ans[N];
signed main(){
srand(time(0));
n=in,m=in;
for(int i=1,u,v;i<=m;i++)
u=in,v=in,rG.insert(v,u);
Q=in;
for(int i=1;i<=Q;i++){
t[i]=in,tu[i]=in;
if(t[i]==1||t[i]==3)tv[i]=in;
}
for(int i=1;i<=Q;i++) ans[i]=1;
for(int T=1;T<=10;T++){
sum=now=0;
for(int i=1;i<=n;i++) suma[i]=0,a[i]=rand(),sum+=a[i];
for(int u=1;u<=n;u++){
FOR(i,u,rG)suma[u]+=a[v];
b[u]=suma[u],now+=b[u];
}
for(int i=1,u,v;i<=Q;i++){
u=tu[i],v=tv[i];
if(t[i]==1)b[v]-=a[u],now-=a[u];
else if(t[i]==2)now-=b[u],b[u]=0;
else if(t[i]==3)b[v]+=a[u],now+=a[u];
else now+=suma[u]-b[u],b[u]=suma[u];
if(now!=sum)ans[i]=0;
}
}
for(int i=1;i<=Q;i++){
if(ans[i])cout<<"YES\n";
else cout<<"NO\n";
}
return 0;
}
T4
给出一棵树,一个点一次可以跳到距离小于等于 \(k\) 的所有点,一条路径权值是跳的所有点点权之和,求树上两点间路径的最小权值。
逐步递进,\(k=1\) 时,就是树上两点距离,\(k=2\) 时,注意到假如跳出了两点间的链,那么跳回来的点在跳出去之前一定可以跳到,所以不会跳出链,\(k=3\) 时,同上,不会跳出去两个点,也就是如果跳出链,只会跳到链周围一圈,而对于一个点,它周围一圈没有区别,所以可以找到一个点周围的 \(\min\) 记作 \(ex[u]\)。
那么假设把这条链找出来了,设 \(dp[u][0/1/2]\) 表示上个跳到的点与 \(u\) 的距离为 \(0/1/2\),然后你发现就可以用 \(dp[u-1]\) 更新 \(dp[u]\) 了。
- \(dp[u][0]=\min\limits_{j=0}^{k-1}(dp[u-1][j]+a[u])\)
- \(dp[u][1]=\min(dp[u-1][0],[k=3](dp[u-1][1]+ex[u]))\)
- \(dp[u][2]=[k\ge2]dp[u-1][1]\)
发现是线性转移,而 \(+\) 对 \(\min\) 有分配律所以可以用矩阵优化转移,可以用倍增维护一个向上和向下的矩阵乘,也可以树剖。
给出一份树剖的实现。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define FOR(u) for(int i=head[u],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
#define int long long
#define in read()
inline int read(){
int p=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))p=p*10+c-48,c=getchar();
return p*f;
}
const int N=200005,inf=200000000000000;
struct edge{int v,nxt;}e[N<<1];
int head[N],en=1;
inline void insert(int u,int v){e[++en]={v,head[u]},head[u]=en;}
int n,Q,k,val[N],ex[N];
inline void ckmn(int &x,int y){if(y<x)x=y;}
struct mat{
int a[3][3];
mat(){for(int i=0;i<3;i++)for(int j=0;j<3;j++)a[i][j]=inf;}
void I(){for(int i=0;i<3;i++)a[i][i]=0;}
mat operator*(mat b){
mat c;
for(int i=0;i<3;i++)
for(int k=0,tmp;k<3;k++)
tmp=a[i][k],
ckmn(c.a[i][0],tmp+b.a[k][0]),
ckmn(c.a[i][1],tmp+b.a[k][1]),
ckmn(c.a[i][2],tmp+b.a[k][2]);
return c;
}
}up[N<<2],down[N<<2];
int dep[N],siz[N],maxp[N],fa[N],top[N],ttol[N],ltot[N],tot;
inline void dfsFi(int u,int f){
dep[u]=dep[f]+1,siz[u]=1,maxp[u]=0,fa[u]=f;
FOR(u)if(v^f) dfsFi(v,u),siz[u]+=siz[v],maxp[u]=siz[v]>siz[maxp[u]]?v:maxp[u];
}
inline void dfsSe(int u,int f,int tp){
top[u]=tp,ttol[u]=++tot,ltot[tot]=u;
if(maxp[u]) dfsSe(maxp[u],u,tp);
FOR(u)if(v^f&&v^maxp[u]) dfsSe(v,u,v);
}
inline int lca(int x,int y){
while(top[x]^top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}if(dep[x]<dep[y])swap(x,y); return y;
}
inline void built(int l,int r,int p){
if(l==r){
int x=ltot[l];
if(k==1) up[p].a[0][0]=val[x];
if(k==2) up[p].a[0][0]=up[p].a[0][1]=val[x],up[p].a[1][0]=0;
if(k==3) up[p].a[0][0]=up[p].a[0][1]=up[p].a[0][2]=val[x],up[p].a[1][0]=up[p].a[2][1]=0,up[p].a[1][1]=ex[x];
down[p]=up[p];
return ;
}
int mid=(l+r)>>1;
built(l,mid,p<<1),built(mid+1,r,p<<1|1);
up[p]=up[p<<1|1]*up[p<<1];
down[p]=down[p<<1]*down[p<<1|1];
}
inline mat queryUp(int l,int r,int p,int ql,int qr){
if(ql<=l&&r<=qr)return up[p];
int mid=(l+r)>>1;
if(qr<=mid)return queryUp(l,mid,p<<1,ql,qr);
if(ql>mid)return queryUp(mid+1,r,p<<1|1,ql,qr);
return queryUp(mid+1,r,p<<1|1,ql,qr)*queryUp(l,mid,p<<1,ql,qr);
}
inline mat queryDown(int l,int r,int p,int ql,int qr){
if(ql<=l&&r<=qr)return down[p];
int mid=(l+r)>>1;
if(qr<=mid)return queryDown(l,mid,p<<1,ql,qr);
if(ql>mid)return queryDown(mid+1,r,p<<1|1,ql,qr);
return queryDown(l,mid,p<<1,ql,qr)*queryDown(mid+1,r,p<<1|1,ql,qr);
}
inline mat queryDown(int x,int y){
mat ans;ans.I();
while(top[x]!=top[y]){
ans=queryDown(1,n,1,ttol[top[x]],ttol[x])*ans;
x=fa[top[x]];
}return queryDown(1,n,1,ttol[y],ttol[x])*ans;
}
mat q[N];int qn;
inline mat queryUp(int x,int y){
mat ans;ans.I();qn=0;
while(top[x]!=top[y]){
q[++qn]=queryUp(1,n,1,ttol[top[x]],ttol[x]);
x=fa[top[x]];
}
if(y!=x) q[++qn]=queryUp(1,n,1,ttol[y]+1,ttol[x]);
while(qn)ans=q[qn--]*ans;
return ans;
}
inline mat query(int x,int y){
int t=lca(x,y);
if(x==t)return queryUp(y,x);
return queryUp(y,t)*queryDown(fa[x],t);
}
signed main(){
n=in,Q=in,k=in;
for(int i=1;i<=n;i++) val[i]=in,ex[i]=inf;
for(int i=1,u,v;i<n;i++) u=in,v=in,insert(u,v),insert(v,u),ckmn(ex[u],val[v]),ckmn(ex[v],val[u]);
dfsFi(1,0),dfsSe(1,0,1),built(1,n,1);
for(int i=1,x,y;i<=Q;i++){
x=in,y=in;
mat ans=query(x,y);
cout<<val[x]+ans.a[0][0]<<'\n';
}
return 0;
}