CSP-S 2022 第二轮 nt 游记 & 题解

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\)

但正解是dpf[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]为要转移到的点,

\[\begin{bmatrix} g_{1,1}& g_{1,2} & g_{1,3} \\ g_{2,1}& g_{2,2} & g_{2,3} \\ g_{3,1}& g_{3,2} & g_{3,3} \end{bmatrix} \]

可以知道当\(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即可。

posted @ 2022-10-31 19:23  WBWYX  阅读(345)  评论(0编辑  收藏  举报