CSP-S 2022 第二轮 nt 游记 & 题解
T1
想了一个小时,想到了一个接近于正解的做法,但最后还是打了个暴力走了。
T2
第一眼以为很难的博弈论,结果线段树水题。
但赛事少考虑了一种情况,导致100->40
,如果数据水说不定能多拿几分。
T3
极其复杂的题面,直接跳过。
做完T4暴力之后,回来看了半天,也打了暴力。
最后暴力。
T4
直接暴力,但感觉比T3简单。
total
应该是稳\(70+40+40+40=190\) 的了,第二题只高不低,估计压线一等/(ㄒoㄒ)/~~,七级蓝勾没了。
题解
T1
不知道怎么拿\(k=0\)的部分分
但不管怎么样,都是先做一遍全源最短路,查看点两两之间是否能直接到达。
接下来如果使用暴力bfs
,可以得到\(70\)的好成绩,
如果使用\(n^4\)暴力,判断一下时间是否大于\(1.9s\),数据水的话应该可以卡到\(100\)
但正解是dp
,f[x][i]
表示从\(1\)开始,经过\(1\)个点到达\(x\)的第i
大值,要确保每个点都不一样。
把这个f
求出来是\(n^2\)的。
设这个\(5\)点环为1->a->b->c->d->1
,枚举b,c
,在枚举第i
大值,\(i=10\)就可以保证是正确的了,还要注意\(a\not=b\not=c\not=d\not=1\)。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define int long long
using namespace std;
void read(int &sum)
{
sum=0;
char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int n,m,g;
struct edge { int x,y,next; }a[2510*2510*2];
struct point { int bz; int w; }p[2510];
int len,last[2510];
int dis[2510][2510];
int dp[2510][15];
int f[2510][15][2];
void ins(int x,int y)
{
len++;
a[len].x=x,a[len].y=y;
a[len].next=last[x],last[x]=len;
}
void spfa(int st)
{
for (int i=1;i<=n;i++) dis[st][i]=-1;
for (int i=1;i<=n;i++) p[i].bz=0;
dis[st][st]=0;
p[st].bz=1;
queue<int> q;
q.push(st);
while (!q.empty())
{
int x=q.front();q.pop();
for (int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if (!p[y].bz && dis[st][x]<=g)
{
dis[st][y]=dis[st][x]+1;
p[y].bz=1;
q.push(y);
}
}
}
}
signed main()
{
// freopen("holiday/holiday3.in","r",stdin);
// freopen("holiday.out","w",stdout);
read(n),read(m),read(g);
for (int i=2;i<=n;i++) scanf("%lld",&p[i].w);
for (int i=1;i<=m;i++)
{
int x,y;read(x),read(y);
ins(x,y),ins(y,x);
}
for (int i=1;i<=n;i++)
spfa(i);
len=0;memset(last,0,sizeof(last));
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (dis[i][j]!=-1 && i!=j)
ins(i,j);
memset(dp,-63,sizeof(dp));
for (int k1=last[1];k1;k1=a[k1].next)
{
int x=a[k1].y;
for (int k2=last[x];k2;k2=a[k2].next)
{
int y=a[k2].y;
if (y==1) continue;
int wh=-1;
for (int i=0;i<=10;i++)
if (p[x].w+p[y].w>dp[y][i])
{
wh=i;
break;
}
if (wh==-1) continue;
for (int i=10;i>=wh+1;i--)
{
dp[y][i]=dp[y][i-1];
f[y][i][0]=f[y][i-1][0];
f[y][i][1]=f[y][i-1][1];
}
dp[y][wh]=p[x].w+p[y].w;
f[y][wh][0]=x,f[y][wh][1]=y;
}
}
int ans=0;
for (int x=2;x<=n;x++)
{
for (int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
for (int i=0;i<=10;i++)
{
for (int j=0;j<=10;j++)
{
int a=f[x][i][0];
int b=f[x][i][1];
int c=f[y][j][1];
int d=f[y][j][0];
// printf("%lld %lld %lld %lld\n",a,b,c,d);
if (a!=c && a!=d && b!=c && b!=d)
ans=max(ans,dp[x][i]+dp[y][j]);
}
}
}
}
printf("%lld\n",ans);
return 0;
}
T2
一眼题,把\(0\)即看成负数,也看成正数。
判断\(9\)种正负存在情况即可。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;
char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
struct node { int mx,mn,s,g; };
struct tree
{
int n;
int a[100010];
int mx[100010*4],mn[100010*4],s[100010*4],g[100010*4];
void updata(int k)
{
mx[k]=max(mx[k*2],mx[k*2+1]);
mn[k]=min(mn[k*2],mn[k*2+1]);
if (s[k*2]>s[k*2+1])
s[k]=s[k*2];
else s[k]=s[k*2+1];
if (g[k*2]<g[k*2+1])
g[k]=g[k*2];
else g[k]=g[k*2+1];
}
void build(int l,int r,int k)
{
if (l>r) return ;
int mid=(l+r)/2;
if (l==r)
{
mx[k]=mn[k]=a[mid];
if (a[mid]>0) g[k]=a[mid],s[k]=-1<<30;
else s[k]=a[mid],g[k]=1<<30;
if (a[mid]==0) s[k]=g[k]=0;
}
else build(l,mid,k*2),build(mid+1,r,k*2+1),updata(k);
}
node ask(int l,int r,int k,int x,int y)
{
if (l>r) return (node){-1<<30,1<<30,-1<<30,1<<30};
if (y<l || r<x) return (node){-1<<30,1<<30,-1<<30,1<<30};
int mid=(l+r)/2;
if (x<=l && r<=y) return (node){mx[k],mn[k],s[k],g[k]};
else
{
node lc=ask(l,mid,k*2,x,y);
node rc=ask(mid+1,r,k*2+1,x,y);
node x;
x.mx=max(lc.mx,rc.mx);
x.mn=min(lc.mn,rc.mn);
if (lc.s>rc.s)
x.s=lc.s;
else x.s=rc.s;
if (lc.g<rc.g)
x.g=lc.g;
else x.g=rc.g;
return x;
}
}
}L,Q;
int n,m,p;
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
read(L.n),read(Q.n),read(p);
for (int i=1;i<=L.n;i++) read(L.a[i]);
for (int i=1;i<=Q.n;i++) read(Q.a[i]);
L.build(1,L.n,1),Q.build(1,Q.n,1);
for (int i=1;i<=p;i++)
{
int x,y,l,r;
read(x),read(y),read(l),read(r);
// printf("?");
node u=L.ask(1,L.n,1,x,y),v=Q.ask(1,Q.n,1,l,r);
// printf("i : %d\n",i);
if (v.mn>=0)
{
if (u.mx<=0) printf("%lld\n",(long long)u.mx*v.mx);
else printf("%lld\n",(long long)u.mx*v.mn);
}
else if (v.mx<=0)
{
if (u.mn>=0) printf("%lld\n",(long long)u.mn*v.mn);
else printf("%lld\n",(long long)u.mn*v.mx);
}
else
{
if (u.s==-1<<30 && u.g==1<<30) printf("0\n");
else if (u.s==-1<<30) printf("%lld\n",(long long)u.g*v.mn);
else if (u.g==1<<30) printf("%lld\n",(long long)u.s*v.mx);
else printf("%lld\n",max((long long)u.s*v.mx,(long long)u.g*v.mn));
}
}
return 0;
}
T3
看起来十分复杂,但如果考虑到每个的出度为\(1\)就可以发动反击的话。
那题目就变成了维护一个数组,每次会把一个点或一组的出度\(\pm1\),判断是否全是\(1\)
可以用hash
判断是否全是\(1\),维护一下出度入度即可。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void write(int sum)
{
if (sum<0) printf("-"),sum=-sum;
if (sum==0) { putchar('0'),putchar(' '); return ; }
char ch[20];int len=0;
while (sum>0) ch[++len]=sum%10+'0',sum/=10;
for (int i=len;i>=1;i--) putchar(ch[i]);
putchar(' ');
}
int n,m,q;
struct graph
{
struct edge { int x,y,next; }a[500010];
int len,last[500010];
void ins(int x,int y)
{
len++;
a[len].x=x,a[len].y=y;
a[len].next=last[x],last[x]=len;
}
}Gt,Gb;
unsigned long long base_=998244353;
unsigned long long ans=0,sum=0;
unsigned long long base[500010];
unsigned long long cd[500010];
unsigned long long rd[500010];
unsigned long long hsc[500010];
unsigned long long hsr[500010];
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
base[0]=1;
for (int i=1;i<=500000;i++) base[i]=base[i-1]*base_;
read(n),read(m);
for (int i=1;i<=m;i++)
{
int x,y;read(x),read(y);
Gt.ins(x,y),Gb.ins(y,x);
}
for (int x=1;x<=n;x++)
{
cd[x]=rd[x]=0;sum+=base[x];
for (int k=Gt.last[x];k;k=Gt.a[k].next)
cd[x]+=base[x];
for (int k=Gb.last[x];k;k=Gb.a[k].next)
rd[x]+=base[Gb.a[k].y];
hsc[x]=cd[x];
hsr[x]=rd[x];
ans+=cd[x];
}
read(q);
for (int i=1;i<=q;i++)
{
int t;read(t);
if (t==1)
{
int x,y;read(x),read(y);
ans-=base[x];
hsc[x]-=base[x];
hsr[y]-=base[x];
}
if (t==2)
{
int x;read(x);
ans-=hsr[x];
hsr[x]=0;
}
if (t==3)
{
int x,y;read(x),read(y);
ans+=base[x];
hsc[x]+=base[x];
hsr[y]+=base[x];
}
if (t==4)
{
int x;read(x);
ans+=rd[x]-hsr[x];
hsr[x]=rd[x];
}
// printf("%llu %llu\n",ans,sum);
if (ans==sum) printf("YES\n");
else printf("NO\n");
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
T4
看到这题,如果是一条链的时候,就是一个dp
了
又因为是一颗树,所以容易想到动态树dp
。
当\(k=1\)时,就是链上所有点的权值。
当\(k\geq2\)时,就要考虑dp
了,
设f[x][1]
表示已经选了的点最近距离为\(1\)时,传输到x
的最小代价,
f[x][2],f[x][3]
同理,
在设a[x][0]
表示x
的权值,
a[x][1]
表示x
和与x
的距离为\(1\)的点中的最小值,
考虑转移矩阵,设f[y]
为要转移到的点,
可以知道当\(k=3\),转移为
\[\left\{\begin{matrix} f[y][1]=\min(f[x][1]+a[x][0],f[x][2]+a[x][0],f[x][3]+a[x][0])\\ f[y][2]=\min(f[x][1],f[x][2]+a[x][1]) \\ f[y][3]=f[x][2] \end{matrix}\right. \]当\(k=3\)时,转移为
\[\left\{\begin{matrix} f[y][1]=\min(f[x][1]+a[x][0],f[x][2]+a[x][0])\\ f[y][2]=f[x][1] \\ f[y][3]=\infty \end{matrix}\right. \]根据不同的情况可以得到不同的\(g\)
做动态树dp
即可。