2025省选模拟3
2025省选模拟3
luogu P10060 [SNOI2024] 树 V 图
-
部分分
:枚举排列数,可以适当添加剪枝,但优化不明显。
点击查看代码
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; }
-
正解
- 显然有
,且同一个 值对应的若干个点一定在一个连通块内。这 个极大连通块必须都出现过且不交。 - 考虑把每个极大连通块
缩成一个点,建出新树后进行转移。 - 具体地,设
表示 的连通块内选择 作为关键点时以 为的根的子树内的方案数。 - 考虑原树边界处(新树上相邻节点)的转移,状态转移方程形如
。观察到 的取值只与 交界处是否合法有关,预处理树上任意两点距离即可。 - 最终,有
即为所求。 - 因每对点对仅被枚举一次,故时间复杂度为
。
点击查看代码
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; }
- 显然有
luogu P10061 [SNOI2024] 矩阵
-
部分分
- 数据点
:暴力。 - 数据点
:二维差分。
点击查看代码
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; }
- 数据点
-
正解
- 延续差分的思想,考虑维护相邻位置的信息,具体地,使用十字链表维护四个方向的位置和其差分数组。
- 此时旋转和修改操作至多会有
个位置会发生变化,且相邻两列/行可以一次遍历同时得到。 - 在旋转时维护旋转后四个方向位置的变化不太好做,不妨给予其新的方向编号,查询时再计算出其被旋转了多少次,以此将旋转也转化为了修改操作。
- 对于修改操作,自哨兵节点遍历至需要修改的
条线段,然后进行修改差分数组。 - 对于旋转操作,同样自哨兵节点遍历至需要修改的
条线段,拼接后后进行修改被旋转次数。 - 略带卡常。
点击查看代码
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; }
luogu P10062 [SNOI2024] 拉丁方
- 需要二分图边染色,等学了再改。
总结
一直在对着自己的枚举排列数尝试优化,为此还写了一维树状数组查询路径信息、二维树状数组查询子树某一深度范围内的信息。
后记
- 下发文件中的样例有些混乱:
里的小样例没有单独下发,大样例则添加了ex
后缀。 赛时没有Special Judge
。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18656170,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下