CSP-S 2022 个人题解
因为疫情没能参加。vp 了一下
假期计划
先做 \(n\) 遍 BFS 算出来 \(\text{dis}(x,y)\) 表示 \(x,y\) 的最短路长度。
然后考虑对每个 \(i\) 算出来 \(1,i\) 均可在 \(k\) 步内到达的点集 \(S\) 中点权最大的值,记为 \(v_i\),接下来只需要枚举满足 \(\text{dis}(b,c)\le k\) 的所有 \(b,c\),然后算出 \(v_b+v_c+s_b+s_c\) 更新答案即可。但是这样是错的,因为可能重复。
仔细思考一下发现至多重复两次,维护前三大的值即可。复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
return x*f;
}
const int N=2515;
int n,m,k;
int dis[N][N],val[N];
vector<int>G[N],T[N];
#define fi first
#define se second
#define mk make_pair
pair<int,int>mx[N][3];
bool vis[N][N];
queue<int>q;
void bfs(int s){
q.push(s),dis[s][s]=0,vis[s][s]=1;
while(q.size()){
int x=q.front();q.pop();
for(int v:G[x]){
if(vis[s][v])continue;
dis[s][v]=dis[s][x]+1,vis[s][v]=1,q.push(v);
}
}
}
bool can[N];//can can need
const int INF=4e18+114514;//show show way
signed main(void){
freopen("holiday.in","r",stdin);
freopen("holiday.out","w",stdout);
memset(dis,63,sizeof(dis));
n=read(),m=read(),k=read()+1;
for(int i=2;i<=n;i++)val[i]=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=n;i++)bfs(i);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++)if(dis[i][j]<=k)T[i].push_back(j),T[j].push_back(i);
}
for(int v:T[1])can[v]=1;
for(int i=2;i<=n;i++)mx[i][0]=mx[i][1]=mx[i][2]=mk(-INF,-1);
for(int i=2;i<=n;i++){
for(int v:T[i]){
if(!can[v])continue;
if(val[v]>mx[i][0].fi)mx[i][2]=mx[i][1],mx[i][1]=mx[i][0],mx[i][0]=mk(val[v],v);
else if(val[v]>mx[i][1].fi)mx[i][2]=mx[i][1],mx[i][1]=mk(val[v],v);
else if(val[v]>mx[i][2].fi)mx[i][2]=mk(val[v],v);
}
}
int ans=0;
for(int i=2;i<=n;i++){
for(int j=2;j<=n;j++){
if(dis[i][j]>k||i==j)continue;int x=0,y=0;
if(mx[i][x].se==j)x++;
if(mx[j][y].se==i)y++;
if(mx[i][x].se!=mx[j][y].se)ans=max(ans,mx[i][x].fi+mx[j][y].fi+val[i]+val[j]);
else{
int now=x;x++;
if(mx[i][x].se==j)x++;
ans=max(ans,mx[i][x].fi+mx[j][y].fi+val[i]+val[j]);
x=now;now=y;y++;
if(mx[j][y].se==i)y++;
ans=max(ans,mx[i][x].fi+mx[j][y].fi+val[i]+val[j]);
}
}
}
cout<<ans<<'\n';
return 0;
}
策略游戏
分别求出 \(A[l_1\cdots r_1],B[l_2\cdots r_2]\) 中正数最大值、正数最小值、负数最大值、负数最小值,那么两人选的数要么是以上四种之一,要么是 \(0\)。暴力枚举两人分别选哪个就行了,使用线段树维护,复杂度 \(O(n+q\log n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
return x*f;
}
const int N=1e5+5;
struct Node{int mx,mn,nx,nn;bool f;};
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
const int INF=1e9+1;
int val[2][N],n,m;
struct SegTree{
Node d[N<<2];
inline Node Merge(Node ls,Node rs){
Node res;res.f=(ls.f|rs.f);
res.mx=max(ls.mx,rs.mx),res.mn=min(ls.mn,rs.mn);
res.nx=max(ls.nx,rs.nx),res.nn=min(ls.nn,rs.nn);
return res;
}
inline void pushup(int p){d[p]=Merge(d[ls(p)],d[rs(p)]);}
inline void build(int l,int r,int p,int c){
if(l==r){
if(val[c][l]>0)d[p].mx=d[p].mn=val[c][l],d[p].nx=-INF,d[p].nn=INF,d[p].f=0;
if(val[c][l]==0)d[p].f=1,d[p].mx=d[p].nx=-INF,d[p].mn=d[p].nn=INF;
if(val[c][l]<0)d[p].mx=-INF,d[p].mn=INF,d[p].nx=d[p].nn=val[c][l],d[p].f=0;
return ;
}
int mid=(l+r)>>1;
build(l,mid,ls(p),c),build(mid+1,r,rs(p),c);pushup(p);
}
inline Node get(int l,int r,int ql,int qr,int p){
if(l<=ql&&qr<=r)return d[p];
int mid=(ql+qr)>>1;
if(l>mid)return get(l,r,mid+1,qr,rs(p));
if(r<=mid)return get(l,r,ql,mid,ls(p));
return Merge(get(l,r,ql,mid,ls(p)),get(l,r,mid+1,qr,rs(p)));
}
}T[2];
vector<int>f,g;
signed main(void){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
n=read(),m=read();int q=read();
for(int i=1;i<=n;i++)val[0][i]=read();
for(int i=1;i<=m;i++)val[1][i]=read();
T[0].build(1,n,1,0),T[1].build(1,m,1,1);
while(q--){
int l=read(),r=read(),x=read(),y=read(),ans=-INF*INF;
Node s=T[0].get(l,r,1,n,1),t=T[1].get(x,y,1,m,1);
f.clear();f.push_back(s.mx),f.push_back(s.nx),f.push_back(s.mn),f.push_back(s.nn);
g.clear();g.push_back(t.mx),g.push_back(t.nx),g.push_back(t.mn),g.push_back(t.nn);
for(int v:f){
if(v==INF||v==-INF)continue;
int res=INF*INF;
for(int w:g)if(w!=INF&&w!=-INF)res=min(res,w*v);
ans=max(ans,res);
}
if(s.f)ans=max(ans,0ll);if(t.f)ans=min(ans,0ll);
cout<<ans<<'\n';
}
return 0;
}
星战
题目等价于判断这张图是不是基环树森林,也就是是否每个点的出度均为 \(1\)。
考虑给每个点 \(i\) 随机一个点权 \(x_i\),然后对于一张图,设 \(\text{deg}_i\) 为点 \(i\) 的出度,我们记它的特征值为
然后我们发现 \(S\) 在四种操作后的变化都很好维护,可以多维护几个 \(S\),然后每次将 \(S\) 与 \(\sum x_i\) 比较即可。
同理还可以维护 \(\text{xor}\) 和此时图中的边数。正确率我不会算,时间复杂度是线性的。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
return x*f;
}
const int N=5e5+5;
const int V=(1ll<<60)-1;
const int W=1e9;
int Xor[N][4],val[N][4],n,tot[4],m,q,deg[N],D[N],Yor[N][4],res[4];
int Sum[N][4],Tum[N][4],Val[N][4],Res[4],Tot[4];
vector<int>G[N];
signed main(void){
freopen("galaxy.in","r",stdin);
freopen("galaxy.out","w",stdout);
srand(19260817);
n=read(),m=read();int now=m;
for(int i=1;i<=n;i++){
for(int j=0;j<4;j++)val[i][j]=((rand()*rand())&V),res[j]^=val[i][j];
for(int j=0;j<4;j++)Val[i][j]=(rand()*rand()%W),Res[j]+=Val[i][j];
}
for(int i=1;i<=m;i++){
int u=read(),v=read();G[u].push_back(v);deg[v]++,D[v]++;
for(int j=0;j<4;j++)Xor[v][j]^=val[u][j],Yor[v][j]^=val[u][j],tot[j]^=val[u][j];
for(int j=0;j<4;j++)Sum[v][j]+=Val[u][j],Tum[v][j]+=Val[u][j],Tot[j]+=Val[u][j];
}
q=read();
while(q--){
int op=read();
if(op==1){
int u=read(),v=read();deg[v]--,now--;
for(int j=0;j<4;j++)Xor[v][j]^=val[u][j],tot[j]^=val[u][j];
for(int j=0;j<4;j++)Sum[v][j]-=Val[u][j],Tot[j]-=Val[u][j];
}
if(op==2){
int u=read();now-=deg[u],deg[u]=0;
for(int j=0;j<4;j++)tot[j]^=Xor[u][j],Xor[u][j]=0;
for(int j=0;j<4;j++)Tot[j]-=Sum[u][j],Sum[u][j]=0;
}
if(op==3){
int u=read(),v=read();deg[v]++,now++;
for(int j=0;j<4;j++)Xor[v][j]^=val[u][j],tot[j]^=val[u][j];
for(int j=0;j<4;j++)Sum[v][j]+=Val[u][j],Tot[j]+=Val[u][j];
}
if(op==4){
int u=read();now+=D[u]-deg[u],deg[u]=D[u];
for(int j=0;j<4;j++)tot[j]^=(Xor[u][j]^Yor[u][j]),Xor[u][j]=Yor[u][j];
for(int j=0;j<4;j++)Tot[j]+=(Tum[u][j]-Sum[u][j]),Sum[u][j]=Tum[u][j];
}
bool ans=(now==n);
for(int j=0;j<4;j++)ans=(ans&(tot[j]==res[j]));
for(int j=0;j<4;j++)ans=(ans&(Tot[j]==Res[j]));
puts(ans?"YES":"NO");
}
return 0;
}
数据传输
当 \(k\le 2\) 的时候,最优情况下一定不会跳出 \(u\to v\) 的这条链。
因此相当于把 \(u\to v\) 路径上的点都拉出来形成一条链,在这条链上选出若干点满足:
- 头尾必须选
- 每相邻 \(k\) 个点就至少要选一个点
要最小化选出的点权和。显然可以暴力 DP 做到 \(O(q\times nk)\) 之类的。
怎么优化呢,显然可以倍增维护矩阵(或者树剖也可以),这里说一个点分做法
我们建出点分树,然后对 \(u,v\) 考虑二者在点分树上的 \(\text{LCA}\),每次在 \(\text{LCA}\) 处统计跨越它的询问。
对于当前的 \(\text{LCA}=\text{rt}\),我们对每个点算出来 \(f_{i,j}\) 表示从 \(\text{rt}\) 到 \(i\) 这条链上,\(\text{rt}\) 开头的第 \(j\) 个点开始,选到 \(i\),满足限制的情况下,选出点权和的最小值。这里 \(j\in [0,k]\)。
然后就可以愉快地在 \(\text{LCA}\) 处算一个 \(\max+\) 卷积合并两个点了,总的时间复杂度为 \(O(nk^2\log n)\)
然而这个方法不兼容 \(k=3\) 的做法,因为 \(k=3\) 时可能会跳出这条链,然后再跳回来。
这个怎么处理呢,我暂时还不知道,先咕着
下面是 vp 的时候写的点分代码,貌似还写挂了,没拿到 \(k=1,2\) 的所有分数,只有 \(40\) 分
UPD:貌似在 \(n,q=2\times 10^5\) 的时候 TLE 了,但是能过 \(5\times 10^4\),我暂且蒙古
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
return x*f;
}
const int N=2e5+5;
vector<int>G[N];
int val[N],f[N][5],m,n,k,sz[N],mx[N],rt=0,all,col[N],ans[N];
bool vis[N];
vector<pair<int,int> >q[N];
vector<pair<int,pair<int,int> > >S;
#define fi first
#define se second
#define mk make_pair
void getroot(int u,int fa){
sz[u]=1,mx[u]=0;
for(int v:G[u]){
if(v==fa||vis[v])continue;
getroot(v,u),sz[u]+=sz[v],mx[u]=max(mx[u],sz[v]);
}
mx[u]=max(mx[u],all-sz[u]);
if(mx[u]<mx[rt]||rt==0)rt=u;
}
void getc(int u,int fa,int c){
col[u]=c;
for(int v:G[u])if(v!=fa&&(!vis[v]))getc(v,u,c);
}
void getquery(int u,int fa){
for(auto t:q[u])if(col[t.fi]!=col[u]&&col[t.fi])S.push_back(mk(t.se,mk(t.fi,u)));
for(int v:G[u])if(v!=fa&&(!vis[v]))getquery(v,u);
}
const int INF=3e14;
int stk[N],top=0;
void getf(int u,int fa){
for(int i=0;i<=k;i++)f[u][i]=INF;
for(int i=0;i<=k;i++){
if(top==i)f[u][i]=min(f[u][i],val[u]);
for(int j=0;j<k;j++)if(top-j>i)f[u][i]=min(f[u][i],f[stk[top-j]][i]+val[u]);
}
stk[++top]=u;for(int v:G[u])if(v!=fa&&(!vis[v]))getf(v,u);top--;
}
void clear(int u,int fa){
for(int i=0;i<=k;i++)f[u][i]=INF;col[u]=0;
for(int v:G[u])if(v!=fa&&(!vis[v]))clear(v,u);
}
void calc(int u){
S.clear();int cnt=0;col[u]=++cnt;
for(int v:G[u])if(!vis[v])getc(v,u,++cnt);
for(int v:G[u])if(!vis[v])getquery(v,u);
for(auto t:q[u])if(col[t.fi])S.push_back(mk(t.se,mk(t.fi,u)));
f[u][0]=val[u];for(int i=1;i<=k;i++)f[u][i]=INF;
stk[++top]=u;for(int v:G[u])if(!vis[v])getf(v,u);top--;
for(auto t:S){
int x=t.se.fi,y=t.se.se,id=t.fi;ans[id]=INF;
for(int i=1;i<k;i++){
for(int j=1;i+j<=k;j++)ans[id]=min(ans[id],f[x][i]+f[y][j]);
}
for(int j=1;j<=k;j++)ans[id]=min(ans[id],f[x][0]+f[y][j]);
for(int i=1;i<=k;i++)ans[id]=min(ans[id],f[x][i]+f[y][0]);
}
for(int v:G[u])if(!vis[v])clear(v,u);col[u]=0;
}
void build(int u){
vis[u]=1;calc(u);
for(int v:G[u]){
if(vis[v])continue;
all=sz[v],rt=0,getroot(v,u),getroot(rt,u),build(v);
}
}
signed main(void){
freopen("transmit.in","r",stdin);
freopen("transmit.out","w",stdout);
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++)val[i]=read();
for(int i=1;i<=n-1;i++){int u=read(),v=read();G[u].push_back(v),G[v].push_back(u);}
for(int i=1;i<=m;i++){int u=read(),v=read();q[u].push_back(mk(v,i));}
all=n,rt=0;getroot(1,0),getroot(rt,0),build(rt);
for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
return 0;
}