CSP-S 2022
假期计划
简要题意:一个关键点可以通过最多 \(k\) 条边到达下一个关键点,要求你选出 \(4\) 个不同关键点使得从 \(1\) 出发并且能回到 \(1\),并最大化选出点的权值和。
首先可以 \(O(n^2\log n)\) 处理出每个点在 \(k\) 条边内能到的点,记为 \(a_{i,j}=\{0,1\}\)。
我们记这 \(4\) 个点为 \(a,b,c,d\)。那么我们可以预处理出来从 \(1\) 到 \(a\) 到 \(b\) 可不可行,以及从 \(c\) 到 \(d\) 到 \(1\) 可不可行。这部分是 \(O(n^2)\) 的。
那么我们直接枚举 \(b,c\) 的话,只需判断 \(a,c\) 和 \(b,d\) 是否相同,就可以统计答案了。如果直接枚举 \(a,d\) 单次是 \(O(n^2)\) 的,总复杂度 \(O(n^4)\)。
考虑优化暴力,我们发现对于 \(b\) 来说,枚举 \(a\) 时只需要避免枚举到 \(c\),所以这里可以前后缀 \(\max\) 来优化。但如果此时 \(a,d\) 对于 \(b,c\) 都是最优值并且 \(a=d\) 的时候我们需要取舍,其中之一取次大值,所以前后缀再记一个次大值就好了。总时间复杂度 \(O(n^2\log n +n^2)\)。
代码:
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;pc('-');}
if(x>9)write(x/10);
pc(x%10+48);
}
const int inf=4187201950435737472;
int n,m,k,ans;
int val[2505];
vector<int>e[2505];
bool a[2505][2505];
int dis[2505];bool vis[2505];
priority_queue< pair<int,int> >q;
void dk(int s)
{
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
q.push({0,s});dis[s]=0;
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u])continue; vis[u]=true;
for(int v:e[u])
if(dis[v]>dis[u]+1)
{dis[v]=dis[u]+1;q.push({-dis[v],v});}
}
for(int t=1;t<=n;++t)
if(dis[t]>0&&dis[t]<=k+1)a[s][t]=true;
}
int pmx[2505][2505],pmn[2505][2505],smx[2505][2505],smn[2505][2505];
signed main()
{
freopen("holiday.in","r",stdin);
freopen("holiday.out","w",stdout);
n=read(),m=read(),k=read();val[0]=-inf;
for(int i=2;i<=n;++i)val[i]=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}for(int i=1;i<=n;++i)dk(i);
for(int i=2;i<=n;++i)
{
int mx=0,mn=0;
for(int j=1;j<=n;++j)
{
if(val[mx]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mx;
else if(val[mx]>val[pmn[i][j]])pmn[i][j]=mx;
if(val[mn]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mn;
else if(val[mn]>val[pmn[i][j]])pmn[i][j]=mn;
if(!a[1][j]||!a[j][i]||i==j)continue;
if(val[j]>val[mx])mn=mx,mx=j;
else if(val[j]>val[mn])mn=j;
}mx=0,mn=0;
for(int j=n;j>=1;--j)
{
if(val[mx]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mx;
else if(val[mx]>val[pmn[i][j]])pmn[i][j]=mx;
if(val[mn]>val[pmx[i][j]])pmn[i][j]=pmx[i][j],pmx[i][j]=mn;
else if(val[mn]>val[pmn[i][j]])pmn[i][j]=mn;
if(!a[1][j]||!a[j][i]||i==j)continue;
if(val[j]>val[mx])mn=mx,mx=j;
else if(val[j]>val[mn])mn=j;
}
}
for(int i=2;i<=n;++i)
{
int mx=0,mn=0;
for(int j=1;j<=n;++j)
{
if(val[mx]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mx;
else if(val[mx]>val[smn[i][j]])smn[i][j]=mx;
if(val[mn]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mn;
else if(val[mn]>val[smn[i][j]])smn[i][j]=mn;
if(!a[j][1]||!a[i][j]||i==j)continue;
if(val[j]>val[mx])mn=mx,mx=j;
else if(val[j]>val[mn])mn=j;
}mx=0,mn=0;
for(int j=n;j>=1;--j)
{
if(val[mx]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mx;
else if(val[mx]>val[smn[i][j]])smn[i][j]=mx;
if(val[mn]>val[smx[i][j]])smn[i][j]=smx[i][j],smx[i][j]=mn;
else if(val[mn]>val[smn[i][j]])smn[i][j]=mn;
if(!a[j][1]||!a[i][j]||i==j)continue;
if(val[j]>val[mx])mn=mx,mx=j;
else if(val[j]>val[mn])mn=j;
}
}
for(int i=2;i<=n;++i)
for(int j=2;j<=n;++j)
{
if(i==j||!a[i][j])continue;
if(pmx[i][j]==smx[j][i])ans=max(ans,val[i]+val[j]+max(val[pmx[i][j]]+val[smn[j][i]],val[pmn[i][j]]+val[smx[j][i]]));
else ans=max(ans,val[i]+val[j]+val[pmx[i][j]]+val[smx[j][i]]);
}
write(ans),pc('\n');
return 0;
}
策略游戏
全场最简单的题。
维护 \(a_{1\dots n}\) 的区间非负最大值,最小值;非正最大值,最小值。维护 \(b_{1\dots m}\) 的区间最大值,最小值。然后分类讨论即可。注意细节。
可以用 st 表来实现,单次询问 \(O(1)\),也可以直接上 \(2\) 棵线段树,单次 \(O(\log n)\)。
我写的线段树,时间复杂度 \(O(n+m+q\log n)\)。
代码:
#include<bits/stdc++.h>
#define pc(x) putchar(x)
#define int long long
#define ls (pos<<1)
#define rs (pos<<1|1)
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;pc('-');}
if(x>9)write(x/10);
pc(x%10+48);
}
const int inf=2e9;
int n,m,q,ans;
int a[100005],b[100005];
struct node
{
int lmx,lmn,rmn,rmx;
node(int Lmx=inf,int Lmn=-inf,int Rmn=inf,int Rmx=-inf){lmx=Lmx;lmn=Lmn;rmn=Rmn;rmx=Rmx;}
};
struct a_tree
{
int lmx[400005],lmn[400005],rmn[400005],rmx[400005];
void build(int pos,int l,int r)
{
if(l==r)
{
if(a[l]<0){lmx[pos]=lmn[pos]=a[l];rmx[pos]=-inf,rmn[pos]=inf;}
else if(a[l]>0){lmx[pos]=inf,lmn[pos]=-inf;rmx[pos]=rmn[pos]=a[l];}
else {lmx[pos]=lmn[pos]=rmx[pos]=rmn[pos]=0;}
return;
}int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
lmx[pos]=min(lmx[ls],lmx[rs]);lmn[pos]=max(lmn[ls],lmn[rs]);
rmn[pos]=min(rmn[ls],rmn[rs]);rmx[pos]=max(rmx[ls],rmx[rs]);
}
node merge(node x,node y)
{
node res;
res.lmx=min(x.lmx,y.lmx);res.lmn=max(x.lmn,y.lmn);
res.rmn=min(x.rmn,y.rmn);res.rmx=max(x.rmx,y.rmx);
return res;
}
node query(int pos,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return (node){lmx[pos],lmn[pos],rmn[pos],rmx[pos]};
int mid=(l+r)>>1;node res;
if(L<=mid)res=merge(res,query(ls,l,mid,L,R));
if(R>mid)res=merge(res,query(rs,mid+1,r,L,R));
return res;
}
}tra;
struct b_tree
{
int mn[400005],mx[400005];
void build(int pos,int l,int r)
{
if(l==r){mn[pos]=mx[pos]=b[l];return;}
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
mn[pos]=min(mn[ls],mn[rs]);mx[pos]=max(mx[ls],mx[rs]);
}
pii merge(pii x,pii y)
{
pii res={inf,-inf};
res.fi=min(x.fi,y.fi);res.se=max(x.se,y.se);
return res;
}
pii query(int pos,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return {mn[pos],mx[pos]};
int mid=(l+r)>>1;pii res={inf,-inf};
if(L<=mid)res=merge(res,query(ls,l,mid,L,R));
if(R>mid)res=merge(res,query(rs,mid+1,r,L,R));
return res;
}
}trb;
signed main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
n=read(),m=read(),q=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=m;++i)b[i]=read();
tra.build(1,1,n);trb.build(1,1,m);
for(int i=1;i<=q;++i)
{
int l1=read(),r1=read(),l2=read(),r2=read();ans=-2e18;
pii tmp1=trb.query(1,1,m,l2,r2);int mn=tmp1.fi,mx=tmp1.se;
node tmp2=tra.query(1,1,n,l1,r1);
int lmx=tmp2.lmx,lmn=tmp2.lmn,rmn=tmp2.rmn,rmx=tmp2.rmx;
if(l1==r1){write(min(a[l1]*mn,a[l1]*mx)),pc('\n');continue;}
if(l2==r2)
{
if(b[l2]<=0){if(lmx!=inf)ans=max(ans,b[l2]*lmx);if(rmn!=inf)ans=max(ans,b[l2]*rmn);}
else {if(rmx!=-inf)ans=max(ans,b[l2]*rmx);if(lmn!=-inf)ans=max(ans,b[l2]*lmn);}
write(ans),pc('\n');continue;
}
if(mn>=0)ans=rmx!=-inf?mn*rmx:mx*lmn;
else if(mx<=0)ans=lmx!=inf?mx*lmx:mn*rmn;
else{if(rmn!=inf)ans=max(ans,mn*rmn);if(lmn!=-inf)ans=max(ans,mx*lmn);}
write(ans),pc('\n');
}return 0;
}
星战
首先可以发现能进行反攻就是满足所有点的出度为 \(1\)。这里就可以拿到暴力的 \(60\) 分了。
证明:由每个点都可以无限次虫洞穿梭可得,每个点的出度不为 \(0\)。由每个据点可以实现连续穿梭可得,每个点的出度为 \(1\)。所以第二个条件包含第一个条件,即我们只需要满足所有点出度为 \(1\) 即可,证毕。
\(1,3\) 操作容易维护,但 \(2,4\) 操作不好维护。我们可以考虑人类智慧做法——哈希。
给每个点随机一个 \(\text{unsigned long long}\) 内的权值 \(a_i\),若点 \(v\) 有若干条边指向自己,那么 \(val_v=\sum a_u\)。
可以发现要满足上面的条件,就是 \(\sum_{i=1}^n val_i=\sum_{i=1}^n a_i\)。
那么简单维护下即可,具体实现可以看代码。复杂度 \(O(n)\)。
代码:
#include<bits/stdc++.h>
#define pc(x) putchar(x)
#define int unsigned long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;pc('-');}
if(x>9)write(x/10);
pc(x%10+48);
}
mt19937_64 rng(time(NULL));
int n,m,q,ans,sum;
int a[500005],s[500005],c[500005];
signed main()
{
freopen("galaxy.in","r",stdin);
freopen("galaxy.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;++i)a[i]=rng(),sum+=a[i];
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
s[v]+=a[u];c[v]+=a[u];ans+=a[u];
}q=read();
for(int i=1;i<=q;++i)
{
int op=read(),u=read(),v;if(op&1)v=read();
if(op==1){c[v]-=a[u];ans-=a[u];}
else if(op==3){c[v]+=a[u];ans+=a[u];}
else if(op==2){ans-=c[u];c[u]=0;}
else if(op==4){ans+=s[u]-c[u];c[u]=s[u];}
puts(ans==sum?"YES":"NO");
}
return 0;
}
数据传输
\(k=1\) 时,就是路径权值和。
\(k=2\) 时,就是路径上选一些点并且相邻点距离不超过 \(2\),最小化权值和。可以简单证明为什么不会选择路径外的点:假如 \(u\) 跳到 \(fa_u\) 的另外一个儿子,那么此时再跳就只能跳到 \(fa_{fa_u}\) 上,这显然不如直接从 \(u\) 跳到 \(fa_{fa_u}\) 更优,证毕。
\(k=3\) 时,就可以跳到路径下一个节点的另一个儿子上去了,我们考虑 dp。
我们设 \(a_i\) 是 \(i\) 点的权值,\(b_{i,0/1}\) 是距离 \(i\) 点 \(0/1\) 的点的最小权值,\(f_{i,0/1/2}\) 表示从初始点走到位于距离 \(i\) 点 \(0/1/2\) 的点上的最小代价。
这里每次询问 \(O(n)\) 的 dp 就可以拿到 \(44\) 分了。再把 \(k=1,k=2\) 的特殊性质分拿到就是 \(76\) 分了。
我们假设现在位于 \(i\) 点,要转移到 \(i+1\) 点。
当 \(k=1\) 时
当 \(k=2\) 时
当 \(k=3\) 时
发现第二维很小,可以用倍增+矩阵来维护。
我们对矩阵乘法重定义:
那么可以写出转移:
当 \(k=1\) 时
当 \(k=2\) 时
当 \(k=3\) 时
分别预处理出向上和向下的倍增矩阵 \(up,dn\),单次询问复杂度为 \(O(3^3\log n)\)。具体实现看代码。
代码:
#include<bits/stdc++.h>
#define pc(x) putchar(x)
#define int long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void write(int x)
{
if(x<0){x=-x;pc('-');}
if(x>9)write(x/10);
pc(x%10+48);
}
const int inf=1e18;
int n,m,q,lg[200005];
vector<int>e[200005];
int a[200005],b[200005][2];
struct matrix
{
int a[3][3];
matrix(int x=0){for(int i=0;i<3;++i)fill(a[i],a[i]+3,inf); if(x){a[0][0]=a[1][1]=a[2][2]=0;}}
int* operator [](int x){return a[x];}
friend matrix operator *(matrix x,matrix y)
{
matrix res;
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)
for(int k=0;k<3;++k)
res[i][j]=min(res[i][j],x[i][k]+y[k][j]);
return res;
}
}up[200005][19],dn[200005][19];
matrix init(int x)
{
matrix res;
if(m==1){res[0][0]=b[x][0];}
else if(m==2){res[0][0]=res[1][0]=b[x][0];res[0][1]=0;}
else if(m==3){res[0][0]=res[1][0]=res[2][0]=b[x][0];res[1][1]=b[x][1];res[0][1]=res[1][2]=0;}
return res;
}
int fa[200005][19],dep[200005];
void dfs(int x,int f)
{
fa[x][0]=f;dep[x]=dep[f]+1;b[x][1]=f?a[f]:inf;
for(int i=1;i<=lg[dep[x]];++i)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int y:e[x])
{
if(y==f)continue;
dfs(y,x);b[x][1]=min(b[x][1],a[y]);
}up[x][0]=dn[x][0]=init(x);
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=lg[dep[x]-dep[y]];i>=0;--i)
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
for(int i=lg[dep[x]];i>=0;--i)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int SON(int x,int y)
{
for(int i=lg[dep[x]-dep[y]];i>=0;--i)
if(dep[fa[x][i]]>dep[y])x=fa[x][i];
return x;
}
matrix jmp(int x,int y,bool op)
{
matrix res(1);
if(dep[x]<=dep[y])return res;
for(int i=lg[dep[x]-dep[y]];i>=0;--i)
if(dep[fa[x][i]]>=dep[y])
res=op?dn[x][i]*res:res*up[x][i],x=fa[x][i];
return res;
}
matrix query(int x,int y)
{
matrix res(1);
if(x==y||fa[x][0]==y||fa[y][0]==x)return res;
int lca=LCA(x,y);if(x!=lca&&y!=lca)res=init(lca);
return jmp(fa[x][0],lca,0)*res*jmp(fa[y][0],lca,1);
}
signed main()
{
freopen("transmit.in","r",stdin);
freopen("transmit.out","w",stdout);
n=read(),q=read(),m=read();lg[0]=-1;
for(int i=1;i<=n;++i)lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;++i)b[i][0]=a[i]=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
e[u].push_back(v);e[v].push_back(u);
}dfs(1,0);
for(int j=1;j<=lg[n];++j)
for(int i=1;i<=n;++i)
up[i][j]=up[i][j-1]*up[fa[i][j-1]][j-1],
dn[i][j]=dn[fa[i][j-1]][j-1]*dn[i][j-1];
for(int i=1;i<=q;++i)
{
int x=read(),y=read();
matrix res;res[0][0]=a[x];
res=res*query(x,y)*init(y);
write(res[0][0]),pc('\n');
}return 0;
}
后记
今年部分分是不是好多啊qwq,似乎不挂分几乎都能300+,然而 HB 的 CSP 没了。