CSP-S2022退役记
这几天抽空把 \(CSP-S\) 的题改了一下,算是明白我是什么东西了。
反正本次 \(CSP-S\) 连暴力都没能写满,我在知道 \(T3\) 暴力怎么写后觉得太麻烦,就去搞 \(T1\) 了,导致一分也没多拿,\(T4\) 的 \(k=1\) 也没打,我是个废物,估分只有 \(170\),就看 \(CCF\) 的机子牛不牛了。
\(T1\;\;假期计划\)
本题场上一眼最短路预处理,\(DP\) 折半合并,但由于没看出来只需要维护前三大即可,所以应该是只拿了 \(70\) 的暴力。
关于正解,我们考虑题目要求
\(1\to A\to B\to C\to D\to 1\)
我们发现前一半和后一半的形式是一样的,所以我们可以同时处理,最后合并。
我们考虑找出对于每个点 \(i\),距离他不超过 \(k\) 的点且距离 \(1\) 不超过 \(k\) 的点 \(j\) ,然后在这若干个点中找出权值前三大的 \(j\)(其实我们这一步就是找出合法的 \((B,A),(C,D)\) 点对)。我们考虑因为会对我们产生影响的只有与其对称的两个点,因此我们找出权值最大的前三个点就一定能保证找到最优解,我们最后合并即可。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
const int N=3000;
int n,m,k;
long long s[N];
int head[N],tot;
struct Node{
int to,nest;
}bian[N<<3];
void add(int x,int y){
bian[++tot]=(Node){y,head[x]};
head[x]=tot;
}
int dis[N][N];
int vis[N];
int q[N],l,r;
void bfs(int st){
l=1,r=0;
for(int i=1;i<=n;i++)dis[st][i]=0x3f3f3f3f,vis[i]=0;
q[++r]=st,vis[st]=1;
dis[st][st]=0;
while(l<=r){
int cc=q[l++];
for(int i=head[cc];i;i=bian[i].nest){
int v=bian[i].to;
if(dis[st][v]>dis[st][cc]+1){
dis[st][v]=dis[st][cc]+1;
if(dis[st][v]>=k+1)continue;
else vis[v]=1,q[++r]=v;
}
}
}
for(int i=1;i<=n;i++){
if(dis[st][i]<=k+1)dis[st][i]=1;
else dis[st][i]=0;
}
}
int maxk[N][10];
void Insert(int pos,int pos2){
if(s[pos2]>s[maxk[pos][1]])maxk[pos][3]=maxk[pos][2],maxk[pos][2]=maxk[pos][1],maxk[pos][1]=pos2;
else if(s[pos2]>s[maxk[pos][2]])maxk[pos][3]=maxk[pos][2],maxk[pos][2]=pos2;
else if(s[pos2]>s[maxk[pos][3]])maxk[pos][3]=pos2;
}
int main(){
freopen("holiday.in","r",stdin);
freopen("holiday.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=2;i<=n;i++)scanf("%lld",&s[i]);
for(int i=1;i<=m;i++){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
add(s1,s2),add(s2,s1);
}
for(int i=1;i<=n;i++)bfs(i);
for(int i=2;i<=n;i++){
for(int j=2;j<=n;j++){
if(i==j)continue;
if(dis[i][j]==0)continue;
if(dis[j][1]==0)continue;
Insert(i,j);
}
}
long long ans=0;
for(int i=2;i<=n;i++){
for(int it1=1;it1<=3;it1++){
if(maxk[i][it1]==0)break;
for(int j=2;j<=n;j++){
if(i==j||j==maxk[i][it1])continue;
if(dis[i][j]==0)continue;
for(int it2=1;it2<=3;it2++){
if(maxk[j][it2]==0)break;
if(i==maxk[j][it2]||j==maxk[j][it2]||maxk[i][it1]==maxk[j][it2])continue;
ans=max(ans,s[i]+s[j]+s[maxk[i][it1]]+s[maxk[j][it2]]);
}
}
}
}
printf("%lld\n",ans);
return 0;
}
\(T2\;\;策略游戏\)
场切题,就大力分讨即可
可能被 \(CCF\) 老爷机卡常的 \(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M=1e5+10;
const int INF=0x3f3f3f3f;
const long long inf=0x3f3f3f3f3f3f3f3f;
int n,m,q;
int A[M],B[M];
struct Segment_tree{
#define lson (rt << 1)
#define rson (rt << 1 | 1)
struct seg{
int l,r,maxk,mink,pre_ze,next_ze;
bool have_zero;
}tree[M<<2];
void pushup(int rt){
tree[rt].have_zero=(tree[lson].have_zero|tree[rson].have_zero);
tree[rt].maxk=max(tree[lson].maxk,tree[rson].maxk);
tree[rt].mink=min(tree[lson].mink,tree[rson].mink);
tree[rt].pre_ze=max(tree[lson].pre_ze,tree[rson].pre_ze);
tree[rt].next_ze=min(tree[lson].next_ze,tree[rson].next_ze);
}
void build(int rt,int l,int r,int *c){
tree[rt].l=l,tree[rt].r=r;
tree[rt].have_zero=0;
tree[rt].maxk=-INF;
tree[rt].mink=INF;
tree[rt].pre_ze=-INF;
tree[rt].next_ze=INF;
if(l==r){
tree[rt].maxk=tree[rt].mink=c[l];
if(c[l]==0)tree[rt].have_zero=1;
else if(c[l]<0){
tree[rt].pre_ze=c[l];
}
else tree[rt].next_ze=c[l];
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid,c);
build(rson,mid+1,r,c);
pushup(rt);
}
int askmax(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].maxk;
int mid=(tree[rt].l+tree[rt].r)>>1;
int ans=-INF;
if(l<=mid)ans=max(ans,askmax(lson,l,r));
if(r>mid)ans=max(ans,askmax(rson,l,r));
return ans;
}
int askmin(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].mink;
int mid=(tree[rt].l+tree[rt].r)>>1;
int ans=INF;
if(l<=mid)ans=min(ans,askmin(lson,l,r));
if(r>mid)ans=min(ans,askmin(rson,l,r));
return ans;
}
bool ze(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].have_zero;
int mid=(tree[rt].l+tree[rt].r)>>1;
int ans=0;
if(l<=mid)ans|=ze(lson,l,r);
if(r>mid)ans|=ze(rson,l,r);
return ans;
}
int pre(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].pre_ze;
int mid=(tree[rt].l+tree[rt].r)>>1;
int ans=-INF;
if(l<=mid)ans=max(ans,pre(lson,l,r));
if(r>mid)ans=max(ans,pre(rson,l,r));
return ans;
}
int next(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].next_ze;
int mid=(tree[rt].l+tree[rt].r)>>1;
int ans=INF;
if(l<=mid)ans=min(ans,next(lson,l,r));
if(r>mid)ans=min(ans,next(rson,l,r));
return ans;
}
}TA,TB;
void work(int l1,int r1,int l2,int r2){
int Maxk1=TA.askmax(1,l1,r1),Mink1=TA.askmin(1,l1,r1);
int Maxk2=TB.askmax(1,l2,r2),Mink2=TB.askmin(1,l2,r2);
if(Maxk1<0&&Mink1<0&&Maxk2>=0&&Mink2>=0){
printf("%lld\n",1ll*Maxk1*Maxk2);return ;
}
if(Maxk1<0&&Mink1<0&&Maxk2>=0&&Mink2<0){
printf("%lld\n",1ll*Maxk1*Maxk2);return ;
}
if(Maxk1<0&&Mink1<0&&Maxk2<0&&Mink2<0){
printf("%lld\n",1ll*Mink1*Maxk2);return ;
}
if(Maxk1>=0&&Mink1>=0&&Maxk2<0&&Mink2<0){
printf("%lld\n",1ll*Mink1*Mink2);return ;
}
if(Maxk1>=0&&Mink1>=0&&Maxk2>=0&&Mink2<0){
printf("%lld\n",1ll*Mink1*Mink2);return ;
}
if(Maxk1>=0&&Mink1>=0&&Maxk2>=0&&Mink2>=0){
printf("%lld\n",1ll*Maxk1*Mink2);return ;
}
if(Maxk1>=0&&Mink1<0&&Maxk2>=0&&Mink2>=0){
printf("%lld\n",1ll*Maxk1*Mink2);return ;
}
if(Maxk1>=0&&Mink1<0&&Maxk2<0&&Mink2<0){
printf("%lld\n",1ll*Mink1*Maxk2);return ;
}
if(Maxk1>=0&&Mink1<0&&Maxk2>=0&&Mink2<0){
bool now=TA.ze(1,l1,r1) ;
if(now)printf("0\n");
else{
int t1=TA.pre(1,l1,r1),t2=TA.next(1,l1,r1);
long long res1=-inf,res2=-inf;
if(t1!=-INF)res1=1ll*t1*Maxk2;
if(t2!=INF)res2=1ll*t2*Mink2;
printf("%lld\n",max(res1,res2));
}
}
}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
for(int i=1;i<=m;i++)scanf("%d",&B[i]);
TA.build(1,1,n,A);
TB.build(1,1,m,B);
for(int i=1;i<=q;i++){
int s1=0,s2=0,s3=0,s4=0;
scanf("%d%d%d%d",&s1,&s2,&s3,&s4);
work(s1,s2,s3,s4);
}
return 0;
}
\(T3\;\;星战\)
这题的暴力就是按题意模拟就行了,不太好打,但也能拿到分的,但我没拿,我大悲。
关于此题目前的正解,我只想说不愧是人类智慧题。
我们考虑反攻的条件,我们发现第二个条件是第一个条件的充要条件,所以说我们只要满足第二个条件就可以输出 \(Yes\),反之输出 \(No\)。我们发现在四种操作中,对于单条边的操作很好处理,复杂度为 \(O(1)\),但对于点的操作很难处理,一不小心就会达到 \(O(n)\) 的复杂度,我们考虑如何用人类智慧去处理这道题。
我们对于每个点随机一个权值 \(a_i\),我们再设每条当前存在的边的边权为其起点的点权 \(b_i\),那么我们满足条件的充要条件为:当前存在于场上的边有 \(n\) 条,并且 \(\sum_{i=1}^{n}b_i=\sum_{i=1}^{n}a_i\),这里的 \(\sum\) 可以是异或,也可以是普通的和,甚至一些满足结合律的其他操作也可以,那么我们就可以通过预处理,使得我们的所有操作的复杂度均为 \(O(1)\) 了。当然,因为我们用到了随机化,所以我们可以对每个点多随机几个权值,当每部分都满足条件时,我们才认为他满足条件,我们也就在一定程度上保证了正确性,减少了由随机所带来的误差。
用异或写的 \(code\),借用了 \(sandom\) 的名字,我很抱歉。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
mt19937 sandom(time(NULL));
unsigned long long get(unsigned long long l,unsigned long long r){return 1llu*(1llu*sandom()*sandom()*sandom()*sandom()*sandom()*sandom()*sandom()*sandom()*sandom()+sandom())%(r-l+1)+l;}
int n,m,Q;
unsigned long long a[N][4],cha;
unsigned long long ans[4],sum[4];
unsigned long long d[N][4],nowd[N][4];
int deg[N],nowdeg[N];
int now;
int main(){
freopen("galaxy.in","r",stdin);
freopen("galaxy.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=0;j<4;j++){
a[i][j]=get(1llu,1000000000000000000llu);
ans[j]^=a[i][j];
}
}
for(int i=1;i<=m;i++){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
for(int j=0;j<4;j++)sum[j]^=a[s1][j],d[s2][j]^=a[s1][j],nowd[s2][j]^=a[s1][j];
nowdeg[s2]++,deg[s2]++;
}
now=m;
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
int opt=0,s1=0,s2=0;
scanf("%d",&opt);
if(opt==1){
scanf("%d%d",&s1,&s2);
for(int j=0;j<4;j++)sum[j]^=a[s1][j],nowd[s2][j]^=a[s1][j];
now--;
nowdeg[s2]--;
}
else if(opt==2){
scanf("%d",&s1);
for(int j=0;j<4;j++)sum[j]^=nowd[s1][j],nowd[s1][j]=0;
now-=nowdeg[s1];
nowdeg[s1]=0;
}
else if(opt==3){
scanf("%d%d",&s1,&s2);
for(int j=0;j<4;j++)sum[j]^=a[s1][j],nowd[s2][j]^=a[s1][j];
now++;
nowdeg[s2]++;
}
else {
scanf("%d",&s1);
for(int j=0;j<4;j++)sum[j]^=nowd[s1][j]^d[s1][j],nowd[s1][j]=d[s1][j];
now+=deg[s1]-nowdeg[s1];
nowdeg[s1]=deg[s1];
}
printf(now==n&&sum[0]==ans[0]&&sum[1]==ans[1]&&sum[2]==ans[2]&&sum[3]==ans[3]?"YES\n":"NO\n");
}
return 0;
}
\(T4\;\;数据传输\)
我们发现 \(k=1\) 的情况就是两点之间的点权和,并且我们发现对于 \(k=2\) 的情况,我们只会在 \(s\to t\) 的这条链上走,因为我们如果不想走一个点的父亲,与其走到他父亲的另一个儿子那里,我们不如直接走到他父亲的父亲那里(因为我们即使走到了他父亲的另一个儿子那,我们不想走父亲,就还得走到他父亲的父亲,因为多走一个点肯定不优,那么我们直接走到他父亲的父亲更优)。
那么我们设 \(f_i\) 为从 \(s\to i\) 的最小答案,那么我们就可以单次 \(O(n)\) 递推了。
对于 \(k=3\) 的情况,我们是可以走到链外再走回来的,所以我们发现这种情况很不好搞,但由于 \(k=3\),所以我们多走的节点只可能是链上的点及其邻接点,所以我们将 \(DP\) 加一个维度。
我们设 \(f_{i,j}(j\in[0,2])\) 为从 \(s\to i\) ,当前位置在距离 \(i\) 为 \(j\) 的地方的最小权值和,我们同时进行预处理,对于每个节点定义 \(a_{i,j}(j\in[0,1])\),其中 \(a_{i,0}\) 即为点权 \(v_i\),\(a_{i,1}\) 即为与 \(i\) 相接的点中最小的点权,那么我们就可发现转移式子了
解释一下 \(DP\) 转移。假设我们当前是向上转移。对于 \(k=2\) 的转移是显然的,对于 \(k=3\) 的转移,第一个是显然的,第二个是因为我们与 \(fa\) 贡献最小临接点可能为 \(i\),也可能为 \(i\) 的其他儿子,第三个和 \(k=2\) 的最后一个转移一样,因为当从距离 \(i\) 为 \(0\) 的点转移到 \(fa\) 时,一定不如直接走到他父亲的父亲优,而且如果 \(f_{i,1}\) 很小,那么他可能在第一步时被作为答案,所以也不会对答案造成影响。
我们发现即使我们推出了状态转移方程,我们的单次复杂度还是最坏 \(O(n)\) 的,所以我们考虑矩阵优化。
我们首先使用广义的矩阵乘法,即设
\(A\times B=C,C_{i,j}=\min_{k=1}^{n} A_{i,k}+B_{k,j}\)。
那么我们有
特别的,我们在 \(s\) 处的初始矩阵为
我们就可以通过倍增或树剖或者其他数据结构来维护矩阵乘,这样复杂度就由单次 \(O(n)\) 变为了单次 \(O(C^3 logn)\) 或 \(O(C^3 log^2n)\) (\(C\) 为矩阵大小,单 \(log\) 的是倍增,双 \(log\) 的为树剖)。
所以我们的最终复杂度即为 \(O(QC^3log^2n)\) 或 \(O(QC^3logn)\)
我的树剖 \(O(QC^3log^2n)\) 的 \(code\)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
long long a[N][2];
int n,Q,k;
struct Graph{
int head[N],tot,dep[N],fa[N],top[N],siz[N],son[N],dfn[N],dian[N],ntime;
struct Node{int to,nest;}bian[N<<1];
void add(int x,int y){bian[++tot]=(Node){y,head[x]};head[x]=tot;}
void get_heavy_son(int x,int f,int depth){
siz[x]=1,fa[x]=f,son[x]=0,dep[x]=depth;
for(int i=head[x];i;i=bian[i].nest){
int v=bian[i].to;
if(v==f)continue;
get_heavy_son(v,x,depth+1);
siz[x]+=siz[v];
if(siz[v]>siz[son[x]])son[x]=v;
}
}
void get_heavy_edge(int x,int tp){
top[x]=tp,dfn[x]=++ntime,dian[ntime]=x;
if(son[x])get_heavy_edge(son[x],tp);
for(int i=head[x];i;i=bian[i].nest){
int v=bian[i].to;
if(v==fa[x]||v==son[x])continue;
get_heavy_edge(v,v);
}
}
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]];
}
return dep[x]<dep[y]?x:y;
}
}G;
struct Matrix{
long long a[3][3];
Matrix(){memset(a,0x3f,sizeof(a));}
friend Matrix operator *(Matrix x,Matrix y){
Matrix z;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
for(int k=0;k<3;k++){
z.a[i][j]=min(z.a[i][j],x.a[i][k]+y.a[k][j]);
}
}
}
return z;
}
}I;
Matrix Begin(int x){
Matrix z;
z.a[0][0]=a[x][0];
return z;
}
Matrix A(int x){
Matrix z;
if(k==1)z.a[0][0]=a[x][0];
else if(k==2)z.a[0][0]=a[x][0],z.a[0][1]=0,z.a[1][0]=a[x][0];
else z.a[0][0]=z.a[1][0]=z.a[2][0]=a[x][0],z.a[0][1]=z.a[1][2]=0,z.a[1][1]=a[x][1];
return z;
}
struct Segment_Tree{
#define lson (rt << 1)
#define rson (rt << 1 | 1)
struct Seg{Matrix tt,rtt;}tree[N<<2];
void pushup(int rt){
tree[rt].tt=tree[lson].tt*tree[rson].tt;
tree[rt].rtt=tree[rson].rtt*tree[lson].rtt;
}
void build(int rt,int l,int r){
if(l==r){
tree[rt].tt=tree[rt].rtt=A(G.dian[l]);
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
}
Matrix ask(int rt,int l,int r,int L,int R,int opt){
if(l>r) return I;
if(l<=L&&R<=r)return ((opt==1)?tree[rt].tt:tree[rt].rtt);
int mid=(L+R)>>1;
Matrix tmp1,tmp2;
tmp1=I,tmp2=I;
if(l<=mid)tmp1=ask(lson,l,r,L,mid,opt);
if(r>mid)tmp2=ask(rson,l,r,mid+1,R,opt);
if(opt==1)return tmp1*tmp2;
else return tmp2*tmp1;
}
}T;
Matrix getans(int x,int y){
Matrix res1,res2;
res1=I,res2=I;
int last=x;
while(G.top[x]!=G.top[y]){
if(G.dep[G.top[x]]<G.dep[G.top[y]]){
res2=T.ask(1,G.dfn[G.top[y]],G.dfn[y],1,G.ntime,1)*res2;
y=G.fa[G.top[y]];
}
else{
if(x==last)res1=res1*T.ask(1,G.dfn[G.top[x]],G.dfn[G.fa[x]],1,G.ntime,2);
else res1=res1*T.ask(1,G.dfn[G.top[x]],G.dfn[x],1,G.ntime,2);
x=G.fa[G.top[x]];
}
}
if(G.dep[x]<G.dep[y]){
if(x==last)res2=T.ask(1,G.dfn[G.son[x]],G.dfn[y],1,G.ntime,1)*res2;
else res2=T.ask(1,G.dfn[x],G.dfn[y],1,G.ntime,1)*res2;
}
else{
if(x==last)res1=res1*T.ask(1,G.dfn[y],G.dfn[G.fa[x]],1,G.ntime,2);
else res1=res1*T.ask(1,G.dfn[y],G.dfn[x],1,G.ntime,2);
}
return res1*res2;
}
long long ask(int s,int t){
Matrix as;as=Begin(s);
Matrix tmp;tmp=getans(s,t);
return (as*tmp).a[0][0];
}
int main(){
freopen("transmit.in","r",stdin);
freopen("transmit.out","w",stdout);
memset(a,0x3f,sizeof(a));
scanf("%d%d%d",&n,&Q,&k);
for(int i=0;i<3;i++)I.a[i][i]=0;
for(int i=1;i<=n;i++)scanf("%lld",&a[i][0]);
for(int i=1;i<=n-1;i++){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
G.add(s1,s2);
G.add(s2,s1);
}
for(int i=1;i<=n;i++){
for(int j=G.head[i];j;j=G.bian[j].nest){
int v=G.bian[j].to;
a[i][1]=min(a[i][1],a[v][0]);
}
}
G.get_heavy_son(1,0,1);
G.get_heavy_edge(1,1);
T.build(1,1,G.ntime);
for(int i=1;i<=Q;i++){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
printf("%lld\n",ask(s1,s2));
}
return 0;
}
总之,这次 \(CSP-S\) 已经结束了,接下来的任务是 \(NOIP\) 了,祝愿 \(HZOI\) \(NOIP\) \(RP++\)。
\(update:\) 成绩出来了 \(175\),还算不幸中的万幸,\(CCF\) 没卡我常数。