2025省选模拟14

2025省选模拟14

题目来源: 2025多校冲刺省选模拟赛16

\(T1\) P1022. 世界⼤战 \(15pts\)

  • 原题: luogu P7294 [USACO21JAN] Minimum Cost Paths P

  • 部分分

    • \(15pts\)\(O(n^{2})\) 预处理 \(O(1)\) 查询。
    点击查看代码
    ll f[2010][2010],c[2010];
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("worldwar.in","r",stdin);
    	freopen("worldwar.out","w",stdout);
    #endif
    	ll n,m,q,x,y,i,j;
    	scanf("%lld%lld",&n,&m);
    	for(i=1;i<=m;i++)   scanf("%lld",&c[i]);
    	memset(f,0x3f,sizeof(f));
    	f[1][1]=0;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m;j++)
    		{
    			f[i+1][j]=min(f[i+1][j],f[i][j]+c[j]);
    			f[i][j+1]=min(f[i][j+1],f[i][j]+i*i);
    		}
    	}
    	scanf("%lld",&q);
    	for(i=1;i<=q;i++)
    	{
    		scanf("%lld%lld",&x,&y);
    		printf("%lld\n",f[x][y]);
    	}
    	return 0;
    }
    
  • 正解

    • 考虑从 \((1,1)\)\((x,y)\) 中间经过的若干中转点 \((x_{1},y_{1}),(x_{2},y_{2}),\dots,(x_{k},y_{k})\) 的贡献。
    • 对于一条竖-横-竖的路径 \((x_{1},y_{1}) \to (x,y_{1}) \to (x,y_{2}) \to (x_{2},y_{2})\) ,其代价为 \(x^{2}(y_{2}-y_{1})+c_{y_{1}}(x-x_{1})+c_{y_{2}}(x_{2}-x)=(y_{2}-y_{1})x^{2}+(c_{y_{2}}-c_{y_{1}})x+c_{y_{2}}x_{2}-c_{y_{1}}x_{1}\) ,其最小值在 \(\frac{c_{y_{2}}-c_{y_{1}}}{2(y_{2}-y_{1})}\) 时取到,具体取值因要求为正整数所以取 \(\max(\min(\operatorname{round}(\frac{c_{y_{2}}-c_{y_{1}}}{2(y_{2}-y_{1})}),1),n)\)
    • \(opt(y_{1},y_{2})\) 等于上式。根据调整法,升序枚举 \(y\) 的同时单调栈维护单调递增的 \(opt(y_{i-1},y_{i})\) ,预处理前缀和后二分得到答案。
    • 特判 \(y=1\) 时的转移。
    点击查看代码
    ll c[200010],sum[200010],ans[200010],s[200010],opt[200010],n,top=0;
    vector<pair<ll,ll> >q[200010];
    ll get_opt(ll u,ll v)
    {
    	return min(max((ll)round((c[v]-c[u])/(v-u)/2.0),1ll),n);
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("worldwar.in","r",stdin);
    	freopen("worldwar.out","w",stdout);
    #endif
    	ll m,k,x,y,pos,i,j;
    	scanf("%lld%lld",&n,&m);
    	for(i=1;i<=m;i++)  scanf("%lld",&c[i]);
    	scanf("%lld",&k);
    	for(i=1;i<=k;i++)
    	{
    		scanf("%lld%lld",&x,&y);
    		if(y==1)  ans[i]=c[1]*(x-1);
    		else  q[y].push_back(make_pair(x,i));
    	}
    	top=s[1]=opt[1]=1;
    	for(i=2;i<=m;i++)
    	{
    		while(top>=2&&opt[top]>=get_opt(s[top],i))  top--;
    		pos=get_opt(s[top],i);
    		top++;  s[top]=i;  opt[top]=pos;
    		sum[top]=sum[top-1]+(pos-opt[top-1])*c[s[top-1]]+(i-s[top-1])*pos*pos;
    		for(j=0;j<q[i].size();j++)
    		{
    			pos=upper_bound(opt+1,opt+1+top,q[i][j].first)-opt-1;
    			ans[q[i][j].second]=sum[pos]+(q[i][j].first-opt[pos])*c[s[pos]]+(i-s[pos])*q[i][j].first*q[i][j].first;
    		}
    	}
    	for(i=1;i<=k;i++)  printf("%lld\n",ans[i]);
    	return 0;
    }
    

\(T2\) P1023. 跳⽔ \(25pts\)

  • 原题: luogu P3549 [POI 2013] MUL-Multidrink

  • 部分分

    • \(Subtask1\) :枚举全排列。
    • \(Subtask2\) :从 \(1\) 每次步长为 \(1\) 往下跳链直至 \(n\) 的父亲, \(n\) 以下的部分则步长为 \(2\) 地跳,特判链底。
    • \(Subtask4\) :输出 \(1 \sim n\)

    因数据过水,代码实测可以通过 \(Subtask3\) 中完全二叉树的部分分。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[4000010];
    int head[2000010],du[2000010],f[2000010],fa[2000010],siz[2000010],dep[2000010],son[2000010],top[2000010],a[2000010],cnt=0;
    void add(int u,int v)
    {
    	cnt++;  e[cnt]=(node){head[u],v};  head[u]=cnt;
    }
    void dfs1(int x,int father)
    {
    	siz[x]=1;
    	fa[x]=father;
    	dep[x]=dep[father]+1;
    	f[dep[x]]=x;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			dfs1(e[i].to,x);
    			siz[x]+=siz[e[i].to];
    			son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
    		}
    	}
    }
    void dfs2(int x,int id)
    {
    	top[x]=id;
    	if(son[x]!=0)
    	{
    		dfs2(son[x],id);
    		for(int i=head[x];i!=0;i=e[i].nxt)
    			if(e[i].to!=fa[x]&&e[i].to!=son[x])  dfs2(e[i].to,e[i].to);
    	}
    }
    int lca(int u,int v)
    {
    	while(top[u]!=top[v])
    	{
    		if(dep[top[u]]>dep[top[v]])  u=fa[top[u]];
    		else  v=fa[top[v]];
    	}
    	return dep[u]<dep[v]?u:v;
    }
    int get_dis(int x,int y)
    {
    	return dep[x]+dep[y]-2*dep[lca(x,y)];
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("jump.in","r",stdin);
    	freopen("jump.out","w",stdout);
    #endif
    	int n,u,v,flag=1,sum=0,i;
    	scanf("%d",&n);
    	for(i=1;i<=n-1;i++)
    	{
    		scanf("%d%d",&u,&v);
    		du[u]++;  du[v]++;
    		add(u,v);  add(v,u);
    	}
    	dfs1(1,0);  dfs2(1,1);
    	if(n<=12||du[1]==n-1)
    	{
    		for(i=1;i<=n;i++)  a[i]=i;
    		do
    		{
    			flag=1;
    			for(i=2;i<=n&&flag==1;i++)  flag&=(get_dis(a[i-1],a[i])<=2);
    			if(flag==1)
    			{
    				for(i=1;i<=n;i++)  printf("%d\n",a[i]);
    				return 0;
    			}
    		}while(next_permutation(a+2,a+n));
    		printf("quake!\n");
    	}
    	else
    	{
    		sum=0;  flag=1;
    		for(i=1;i<=n;i++) 
    		{
    			sum+=(du[i]==1);
    			flag&=(du[i]<=2);
    		} 
    		if(sum==2&&flag==1)
    		{
    			for(i=1;i<=dep[n]-1;i++)  printf("%d\n",f[i]);
    			for(i=dep[n]+1;i<=n;i+=2)  printf("%d\n",f[i]);
    			if(i==n+1)  printf("%d\n",f[n]);
    			for(i=n-2;i>=dep[n];i-=2)  printf("%d\n",f[i]);
    		}
    		else  printf("quake!\n");
    	}
    	return 0;
    }
    
  • 正解

    • 考虑将 \(1 \sim n\) 这条根链上的点提出来单独考虑。划分成的若干个部分相互独立。
    • \(f_{x,0/1/2/3}\) 分别表示能否 \(x\)\(x\) 出/ \(x\)\(x\) 的儿子出/ \(x\) 的儿子进 \(x\) 的儿子出/ \(x\) 的儿子进 \(x\) 出。特别地,当儿子中只有一个非叶子节点时,令 \(f_{x,2}=f_{x,1}\)
      • \(x\)\(x\) 出,要求 \(x\) 必须是叶子节点。
      • \(x\)\(x\) 的儿子出,要求先到达 \(x\) ,然后到达儿子中的非叶子节点(如果有则必须只能有一个),最后跳到叶子节点。
      • \(x\) 的儿子进 \(x\) 的儿子出,在判掉必须有两个非叶子节点后,要求先到达第一个非叶子节点,然后到达 \(x\) ,接着到达第二个非叶子节点,最后跳到叶子节点。
      • \(x\) 的儿子进 \(x\) 出,要求先到达儿子中的叶子节点,再到达非叶子节点,最后跳到 \(x\)
    • \(f_{x,1}\)\(f_{x,3}\) 本质相同,只是构造方案时搜索顺序的差别,故可以合并成一种情况。
    • \(g_{x,0/1},x \in (1 \to n)\) 分别表示 \(x\) 的子树遍历完后能否停在 \(x\) / \(x\) 的儿子。
      • \(x\) 出要求 \(fa_{x}\) 能出且 \(x\) 能出或 \(fa_{x}\) 的其他儿子能出且 \(x\)\(x\) 出。
        • \(fa_{x}\) 能出且 \(x\)\(x\) 出,从 \(fa_{x}\) 跳到 \(x\) 这个叶子节点。
        • \(fa_{x}\) 能出且 \(x\) 的儿子进 \(x\) 出,从 \(fa_{x}\) 跳到 \(x\) 能进的儿子,再跳到 \(x\)
        • \(fa_{x}\) 的儿子能出要求必须 \(x\)\(x\) 出,否则就跳不出来了。
      • \(x\) 的儿子出要求 \(fa_{x}\) 能出且 \(x\) 的儿子能出或 \(fa_{x}\) 的其他儿子能出且 \(x\)\(x\) 的儿子出。
        • \(fa_{x}\) 能出且 \(x\)\(x\) 的儿子出,从 \(fa_{x}\) 跳到 \(x\) ,再跳到能出的儿子。
        • \(fa_{x}\) 能出且 \(x\) 的儿子进 \(x\) 的儿子出,从 \(fa_{x}\) 跳到 \(x\) 能进的儿子,再跳到 \(x\) ,再跳到另一个能出的儿子。
        • \(fa_{x}\) 的儿子能出且 \(x\)\(x\) 的儿子出,从 \(fa_{x}\) 的儿子跳到 \(x\) ,再跳到能出的儿子。
    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[4000010];
    int head[2000010],f[3][2000010],g[2][2000010],vis[2000010],n,cnt=0;
    vector<int>s,d;
    void add(int u,int v)
    {
    	cnt++;  e[cnt]=(node){head[u],v};  head[u]=cnt;
    }
    void dfs(int x,int fa)
    {
    	s.push_back(x);
    	if(x==n)  d=s;
    	for(int i=head[x];i!=0;i=e[i].nxt)  if(e[i].to!=fa)  dfs(e[i].to,x);
    	s.pop_back();
    }
    void dp(int x,int fa)
    {
    	int son=0,sum=0;
    	f[0][x]=f[1][x]=f[2][x]=1;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa&&vis[e[i].to]==0)
    		{
    			son++;
    			dp(e[i].to,x);
    			sum+=(f[0][e[i].to]==0&&f[1][e[i].to]==1);
    			f[1][x]&=(f[0][e[i].to]|f[1][e[i].to]);
    			f[2][x]&=(f[0][e[i].to]|f[1][e[i].to]);
    		}
    	}
    	f[1][x]&=(sum<=1);  f[2][x]&=(sum<=2);
    	if(son==0)  f[1][x]=f[2][x]=0;
    	else  f[0][x]=0;
    }
    void print_f(int x,int op,int fa)
    {
    	if(op==0)  printf("%d\n",x);
    	if(op==1)
    	{
    		printf("%d\n",x);
    		for(int i=head[x];i!=0;i=e[i].nxt)
    			if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==0)  print_f(e[i].to,3,x);
    		for(int i=head[x];i!=0;i=e[i].nxt)
    			if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==1)  print_f(e[i].to,0,x);
    	}
    	if(op==2)
    	{
    		int sum=0;
    		for(int i=head[x];i!=0;i=e[i].nxt)
    			if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==0)  sum++;
    		if(sum==1)  print_f(x,1,fa);
    		else
    		{
    			for(int i=head[x];i!=0;i=e[i].nxt)
    			{	
    				if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==0)
    				{
    					if(sum==2)
    					{
    						print_f(e[i].to,1,x);
    						printf("%d\n",x);
    						sum++;
    					}
    					else  print_f(e[i].to,3,x);
    				}
    			}
    			for(int i=head[x];i!=0;i=e[i].nxt)
    				if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==1)  print_f(e[i].to,0,x);
    		}
    	}
    	if(op==3)
    	{
    		for(int i=head[x];i!=0;i=e[i].nxt)
    			if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==1)  print_f(e[i].to,0,x);
    		for(int i=head[x];i!=0;i=e[i].nxt)
    			if(e[i].to!=fa&&vis[e[i].to]==0&&f[0][e[i].to]==0)  print_f(e[i].to,1,x);
    		printf("%d\n",x);
    	}
    }
    void print_g(int x,int op)
    {
    	if(x==0)  print_f(d[x],op,0);
    	else
    	{
    		if(op==0)
    		{
    			if(g[0][x-1]==1&&f[0][d[x]]==1)
    			{
    				print_g(x-1,0);  print_f(d[x],0,0);
    			}
    			else  if(g[0][x-1]==1&&f[1][d[x]]==1)
    			{
    				print_g(x-1,0);  print_f(d[x],3,0);
    			}
    			else  
    			{
    				print_g(x-1,1);  print_f(d[x],0,0);
    			}
    		}
    		else
    		{
    			if(g[0][x-1]==1&&f[1][d[x]]==1)
    			{
    				print_g(x-1,0);  print_f(d[x],1,0);
    			}
    			else  if(g[0][x-1]==1&&f[2][d[x]]==1)
    			{
    				print_g(x-1,0);  print_f(d[x],2,0);
    			}
    			else  
    			{
    				print_g(x-1,1);  print_f(d[x],1,0);
    			}
    		}
    	}
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("jump.in","r",stdin);
    	freopen("jump.out","w",stdout);
    #endif
    	int u,v,i;
    	scanf("%d",&n);
    	for(i=1;i<=n-1;i++)
    	{
    		scanf("%d%d",&u,&v);
    		add(u,v);  add(v,u);
    	}
    	dfs(1,0);
    	for(i=0;i<d.size();i++)  vis[d[i]]=1;
    	for(i=0;i<d.size();i++)  dp(d[i],0);
    	g[0][0]=f[0][1];  g[1][0]=f[1][1];
    	for(i=1;i<d.size();i++)
    	{
    		g[0][i]=(g[0][i-1]&(f[0][d[i]]|f[1][d[i]]))|(g[1][i-1]&f[0][d[i]]);
    		g[1][i]=(g[0][i-1]&(f[1][d[i]]|f[2][d[i]]))|(g[1][i-1]&f[1][d[i]]);
    	}
    	if(g[0][d.size()-1]==1)  print_g(d.size()-1,0);
    	else  printf("quake!\n");
    	return 0;
    }
    

\(T3\) P1024. 蛤蟆的近亲 \(40pts\)

  • 先让 \(a_{i}\)\(n+10\)\(\min\) 缩小值域。

  • 部分分

    • \(40pts\)\(O(n^{3})\) 预处理 \(O(1)\) 查询。
    点击查看代码
    int a[510],mex[510][510],w[510][510];
    uint s[510][510];
    bitset<100210>vis,f;
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("frog.in","r",stdin);
    	freopen("frog.out","w",stdout);
    #endif
    	int n,m,i,j,k;
    	uint l,r,ans=0;
    	scanf("%d%d",&n,&m);
    	for(i=1;i<=n;i++)  
    	{
    		scanf("%d",&a[i]);
    		if(a[i]>=100010)  a[i]=100010;
    	}
    	for(i=1;i<=n;i++)
    	{
    		vis.set();
    		for(j=i;j<=n;j++)
    		{
    			vis[a[j]]=0;
    			mex[i][j]=vis._Find_first();
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		f.reset();
    		for(j=i;j<=n;j++)
    		{
    			for(k=i;k<=j;k++)  f[mex[k][j]]=1;
    			w[i][j]=f.count();
    		}
    	}
    	for(i=n;i>=1;i--)
    	{
    		for(j=1;j<=n;j++)  s[i][j]=s[i+1][j]+s[i][j-1]-s[i+1][j-1]+w[i][j];
    	}
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d",&l,&r);
    		l=(l^ans)%n+1;  r=(r^ans)%n+1;
    		if(l>r)  swap(l,r);
    		ans=s[l][r];
    		printf("%u\n",ans);
    	}
    	return 0;
    }
    
  • 正解

总结

  • \(T1\) \(c_{[2 \sim m]}\) 的部分分想简单了,以为只会存在两种走的方式。
  • \(T2\)
    • 为图方便把链底的特判和往下跳的过程合并在了一起 i=min(i+2,n) 然后忘退出了,挂了 \(15pts\)
    • 胡出来一个拆成入点和出点后跑路径匹配的有负圈的费用流(但全程流量为 \(1\) )做法,没时间再写了。
posted @ 2025-02-23 21:19  hzoi_Shadow  阅读(323)  评论(3)    收藏  举报
扩大
缩小