2025省选模拟3

2025省选模拟3

T1 luogu P10060 [SNOI2024] 树 V 图 20pts

  • 部分分

    • 20pts :枚举排列数,可以适当添加剪枝,但优化不明显。
    点击查看代码
    const int p=998244353;
    struct node
    {
        int nxt,to;
    }e[6010];
    int head[3010],f[3010],used[3010],cnt=0,ans=0;
    pair<int,int>g[3010];
    vector<int>pos[3010];
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(int x,int fa)
    {
        g[x]=(used[x]==0)?make_pair(0,0x7f7f7f7f):make_pair(used[x],0);
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                int y=e[i].to;
                dfs(y,x);
                if((g[y].second+1<g[x].second)||(g[y].second+1==g[x].second&&g[y].first<g[x].first))
                {
                    g[x]=make_pair(g[y].first,g[y].second+1);
                }
            }
        }
    }
    void reroot(int x,int fa)
    {
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                int y=e[i].to;
                if((g[x].second+1<g[y].second)||(g[x].second+1==g[y].second&&g[x].first<g[y].first))
                {
                    g[y]=make_pair(g[x].first,g[x].second+1);
                }
                reroot(e[i].to,x);
            }
        }
    }
    void search(int len,int n,int m)
    {
        if(len==m+1)
        {
            dfs(1,0);
            reroot(1,0);
            int flag=1;
            for(int i=1;i<=n;i++)
            {
                flag&=(f[i]==g[i].first);
            }
            if(flag==1)
            {
                ans=(ans+1)%p;
            }
        }
        else
        {
            for(int i=0;i<pos[len].size();i++)
            {
                if(used[pos[len][i]]==0)
                {
                    used[pos[len][i]]=len;
                    search(len+1,n,m);
                    used[pos[len][i]]=0;
                }
            }
        }
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("voronoi.in","r",stdin);
        freopen("voronoi.out","w",stdout);
    #endif
        int t,n,k,u,v,i,j;
        cin>>t;
        for(j=1;j<=t;j++)
        {
            cnt=ans=0;
            memset(e,0,sizeof(e));
            memset(head,0,sizeof(head));
            cin>>n>>k;
            for(i=1;i<=n-1;i++)
            {
                cin>>u>>v;
                add(u,v);
                add(v,u);
            }
            for(i=1;i<=n;i++)
            {
                cin>>f[i];
                pos[i].clear();	
            }
            for(i=1;i<=n;i++)
            {
                pos[f[i]].push_back(i);
            }
            search(1,n,k);
            cout<<ans<<endl;
        }
        return 0;
    }
    
    
  • 正解

    • 显然有 f(ai)=i ,且同一个 f 值对应的若干个点一定在一个连通块内。这 k 个极大连通块必须都出现过且不交。
    • 考虑把每个极大连通块 Si 缩成一个点,建出新树后进行转移。
    • 具体地,设 fu,x 表示 f=u 的连通块内选择 x 作为关键点时以 u 为的根的子树内的方案数。
    • 考虑原树边界处(新树上相邻节点)的转移,状态转移方程形如 fu,x=vSon(x)ySvfv,yval(u,x,v,y) 。观察到 val(u,x,v,y) 的取值只与 u,v 交界处是否合法有关,预处理树上任意两点距离即可。
    • 最终,有 xS1f1,x 即为所求。
    • 因每对点对仅被枚举一次,故时间复杂度为 O(n2)
    点击查看代码
    const int p=998244353;
    struct node
    {
        int nxt,to;
    }e[6010];
    int head[3010],a[3010],b[3010][3010],dis[3010][3010],f[3010][3010],u[3010],v[3010],cnt=0,tot;
    vector<int>g[3010],s[3010];
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void get_dis(int x,int fa,int rt)
    {
        dis[rt][x]=dis[rt][fa]+1;
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                get_dis(e[i].to,x,rt);
            }
        }
    }
    void dfs(int x,int fa)
    {
        tot++;
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa&&a[e[i].to]==a[x])
            {
                dfs(e[i].to,x);
            }
        }
    }
    bool check(int x,int y,int rt)
    {
        return (dis[x][rt]==dis[y][rt])?(a[x]<a[y]):(dis[x][rt]<dis[y][rt]);
    }
    void dp(int u,int fa)
    {
        for(int i=0;i<s[u].size();i++)
        {
            f[u][s[u][i]]=1;
        }
        for(int i=0;i<g[u].size();i++)
        {	
            if(g[u][i]!=fa)
            {
                int v=g[u][i];
                dp(v,u);	
                for(int x=0;x<s[u].size();x++)
                {
                    int sum=0;
                    for(int y=0;y<s[v].size();y++)
                    {
                        if(check(s[u][x],s[v][y],b[u][v])==true&&check(s[u][x],s[v][y],b[v][u])==false)
                        {
                            sum=(sum+f[v][s[v][y]])%p;
                        }
                    }
                    f[u][s[u][x]]=1ll*f[u][s[u][x]]*sum%p;
                }
            }
        }
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("voronoi.in","r",stdin);
        freopen("voronoi.out","w",stdout);
    #endif
        int t,n,k,ans,flag,i,j;
        cin>>t;
        for(j=1;j<=t;j++)
        {
            cnt=ans=0;
            flag=1;
            memset(e,0,sizeof(e));
            memset(head,0,sizeof(head));
            cin>>n>>k;
            for(i=1;i<=n-1;i++)
            {
                cin>>u[i]>>v[i];
                add(u[i],v[i]);
                add(v[i],u[i]);
            }
            for(i=1;i<=n;i++)
            {
                cin>>a[i];
                s[i].clear();
                g[i].clear();
                get_dis(i,0,i);
            }
            for(i=1;i<=n;i++)
            {
                s[a[i]].push_back(i);
            }	
            for(i=1;i<=k&&flag==1;i++)
            {
                if(s[i].size()==0)
                {
                    flag=0;
                }
                else
                {
                    tot=0;
                    dfs(s[i][0],0);
                    flag&=(tot==s[i].size());
                }
            }
            if(flag==1)
            {
                for(i=1;i<=n-1;i++)
                {
                    if(a[u[i]]!=a[v[i]])
                    {
                        g[a[u[i]]].push_back(a[v[i]]);
                        g[a[v[i]]].push_back(a[u[i]]);
                        b[a[u[i]]][a[v[i]]]=u[i];
                        b[a[v[i]]][a[u[i]]]=v[i];
                    }
                }
                dp(1,0);
                for(i=0;i<s[1].size();i++)
                {
                    ans=(ans+f[1][s[1][i]])%p;
                }
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

T2 luogu P10061 [SNOI2024] 矩阵 20pts

  • 部分分

    • 数据点 1 :暴力。
    • 数据点 2 :二维差分。
    点击查看代码
    const ll p=1000000007;
    ll a[3010][3010],b[3010][3010],d[3010][3010];
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    void rev(ll x1,ll y1,ll x2,ll y2)
    {
        ll d=x2-x1+1;
        for(ll i=1;i<=d;i++)
        {
            for(ll j=1;j<=d;j++)
            {
                b[i][j]=a[x1+i-1][y1+j-1];
            }
        }
        for(ll i=1;i<=d;i++)
        {
            for(ll j=1;j<=d;j++)
            {
                a[x1+i-1][y1+j-1]=b[j][d-i+1];
            }
        }
    }
    void add1(ll x1,ll y1,ll x2,ll y2,ll val)
    {
        for(ll i=x1;i<=x2;i++)
        {
            for(ll j=y1;j<=y2;j++)
            {
                a[i][j]=(a[i][j]+val)%p;
            }
        }
    }
    void add2(ll x1,ll y1,ll x2,ll y2,ll val)
    {
        d[x1][y1]=(d[x1][y1]+val)%p;
        d[x2+1][y1]=(d[x2+1][y1]-val+p)%p;
        d[x1][y2+1]=(d[x1][y2+1]-val+p)%p;
        d[x2+1][y2+1]=(d[x2+1][y2+1]+val)%p;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("matrix.in","r",stdin);
        freopen("matrix.out","w",stdout);
    #endif
        ll n,m,pd,x1,y1,x2,y2,val,ans=0,i,j;
        scanf("%lld%lld",&n,&m);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                a[i][j]=qpow(i+1,j,998244353);
            }
        }
        if(n>=1100)
        {
            for(i=1;i<=m;i++)
            {
                scanf("%lld%lld%lld%lld%lld",&pd,&x1,&y1,&x2,&y2);
                if(pd==1)
                {
                    rev(x1,y1,x2,y2);
                }
                else
                {
                    scanf("%lld",&val);
                    add2(x1,y1,x2,y2,val);
                }
            }
        }
        else
        {
            for(i=1;i<=m;i++)
            {
                scanf("%lld%lld%lld%lld%lld",&pd,&x1,&y1,&x2,&y2);
                if(pd==1)
                {
                    rev(x1,y1,x2,y2);
                }
                else
                {
                    scanf("%lld",&val);
                    add1(x1,y1,x2,y2,val);
                }
            }
        }
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                d[i][j]=(d[i][j]+d[i-1][j]+d[i][j-1]-d[i-1][j-1]+p)%p;
                a[i][j]=(a[i][j]+d[i][j])%p;
                ans=(ans+a[i][j]*qpow(12345,(i-1)*n+j,p)%p)%p;
            }
        }
        printf("%lld\n",ans);
        return 0;
    }
    
  • 正解

    • 延续差分的思想,考虑维护相邻位置的信息,具体地,使用十字链表维护四个方向的位置和其差分数组。
    • 此时旋转和修改操作至多会有 O(n) 个位置会发生变化,且相邻两列/行可以一次遍历同时得到。
    • 在旋转时维护旋转后四个方向位置的变化不太好做,不妨给予其新的方向编号,查询时再计算出其被旋转了多少次,以此将旋转也转化为了修改操作。
    • 对于修改操作,自哨兵节点遍历至需要修改的 8 条线段,然后进行修改差分数组。
    • 对于旋转操作,同样自哨兵节点遍历至需要修改的 8 条线段,拼接后后进行修改被旋转次数。
    • 略带卡常。
    点击查看代码
    const int p=1000000007,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};// 0,1,2,3 右,下,左,上
    struct node
    {
    	int nxt[4],dir[4];// 实际顺序已经乱掉,给予其新的 dir 编号后确定真实方向
    	ll d[4];
    }e[10000010];
    struct quality
    {
    	int nxt,dir;
    	ll val;
    }v1[3010],v2[3010],v3[3010],v4[3010],v5[3010],v6[3010],v7[3010],v8[3010];
    int a[3010][3010],n;
    inline void move(int &x,ll &val,int &dir,int op)
    {
    	int tmp=(op+dir)&3;
    	val+=e[x].d[tmp];
    	dir=(e[x].dir[tmp]-op+4)&3;//找到新的方向编号
    	x=e[x].nxt[tmp];
    }
    inline void split(int pos,quality v[],int op)
    {
    	// op=1 分裂行
    	// op=0 分裂列
    	int x=1,dir=0;
    	ll val=0;
    	for(int i=1;i<=pos;i++)  move(x,val,dir,op);
    	op^=1;
    	for(int i=1;i<=n;i++)
    	{
    		move(x,val,dir,op);
    		v[i].nxt=x;
    		v[i].val=val;
    		v[i].dir=dir;
    	}
    }
    inline void get(int pos,quality v1[],quality v2[],int l,int r,int op)
    {
    	split(pos,v1,op);
    	for(int i=l;i<=r;i++)
    	{
    		v2[i]=v1[i];
    		move(v2[i].nxt,v2[i].val,v2[i].dir,op);// 下一行/列
    	}
    }
    inline void change(int x,ll val1,int dir1,int y,ll val2,int dir2,int op)
    {
    	int tmp1=(dir1+op)&3,tmp2=(dir2+op+2)&3;
    	e[x].nxt[tmp1]=y;
    	e[x].d[tmp1]=val2-val1;
    	e[x].dir[tmp1]=(tmp2+2)&3;// +2 确定对立方向
    	e[y].nxt[tmp2]=x;
    	e[y].d[tmp2]=val1-val2;
    	e[y].dir[tmp2]=(tmp1+2)&3;
    }
    inline void add(quality x,quality y,ll val,int dir,int op)
    {
    	change(x.nxt,x.val+val,(x.dir+dir)&3,y.nxt,y.val,y.dir,op);
    }
    inline void rotate(int x1,int y1,int x2,int y2)
    {
    	get(y1-1,v7,v3,x1,x2,0);
    	get(y2,v1,v5,x1,x2,0);
    	get(x1-1,v8,v4,y1,y2,1);
    	get(x2,v2,v6,y1,y2,1);
    	for(int i=x1;i<=x2;i++)  add(v2[y2-i+x1],v5[i],0,1,0);// 下 -> 右
    	for(int i=y1;i<=y2;i++)  add(v3[x1+i-y1],v6[i],0,1,1);// 左 -> 下
    	for(int i=x1;i<=x2;i++)  add(v4[y2-i+x1],v7[i],0,1,2);// 上 -> 左
    	for(int i=y1;i<=y2;i++)  add(v1[x1+i-y1],v8[i],0,1,3);// 右 -> 上
    }
    inline void update(int x1,int y1,int x2,int y2,int val)
    {
    	get(y1-1,v7,v3,x1,x2,0);// 上
    	get(y2,v1,v5,x1,x2,0);// 右
    	get(x1-1,v8,v4,y1,y2,1);// 左
    	get(x2,v2,v6,y1,y2,1);// 下
    	for(int i=x1;i<=x2;i++)  add(v1[i],v5[i],val,0,0);// 右
    	for(int i=y1;i<=y2;i++)  add(v2[i],v6[i],val,0,1);// 下
    	for(int i=x1;i<=x2;i++)  add(v3[i],v7[i],val,0,2);// 左
    	for(int i=y1;i<=y2;i++)  add(v4[i],v8[i],val,0,3);// 上
    }
    inline int get_id(int x,int y)
    {
    	return x*(n+1)+y+1;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("matrix.in","r",stdin);
    	freopen("matrix.out","w",stdout);
    #endif
    	int m,op,x1,x2,y1,y2,ans=0,i,j,k;
    	ll val;
    	scanf("%d%d",&n,&m);
    	for(i=1;i<=n;i++)
    	{
    		for(j=1,val=1;j<=n;j++)
    		{
    			val=val*(i+1)%998244353;
    			a[i][j]=val;
    		}
    	}
    	for(i=0;i<=n;i++)
    	{
    		for(j=0;j<=n;j++)
    		{
    			for(k=0;k<=3;k++)
    			{
    				x1=(i+dx[k]+n+1)%(n+1);
    				y1=(j+dy[k]+n+1)%(n+1);
    				e[get_id(i,j)].nxt[k]=get_id(x1,y1);
    				e[get_id(i,j)].d[k]=a[x1][y1]-a[i][j];
    				e[get_id(i,j)].dir[k]=k;
    			}
    		}
    	}
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d%d%d%d",&op,&x1,&y1,&x2,&y2);
    		if(op==1)
    		{
    			rotate(x1,y1,x2,y2);
    		}
    		else
    		{
    			scanf("%lld",&val);
    			update(x1,y1,x2,y2,val);
    		}
    	}
    	for(i=1,val=1;i<=n;i++)
    	{
    		split(i,v1,1);
    		for(j=1;j<=n;j++)
    		{
    			val=val*12345%p;
    			ans=(ans+v1[j].val%p*val%p)%p;
    		}
    	}
    	printf("%d\n",ans);
    	return 0;
    }
    

T3 luogu P10062 [SNOI2024] 拉丁方 0pts

  • 需要二分图边染色,等学了再改。

总结

  • T1 一直在对着自己的枚举排列数尝试优化,为此还写了一维树状数组查询路径信息、二维树状数组查询子树某一深度范围内的信息。

后记

  • 下发文件中的样例有些混乱: PDF 里的小样例没有单独下发,大样例则添加了 ex 后缀。
  • T3 赛时没有 Special Judge
posted @   hzoi_Shadow  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
扩大
缩小
点击右上角即可分享
微信分享提示