2025省选模拟13

2025省选模拟13

T1 P1025. Easy Problem 40pts

  • 部分分

    • 40pts
      • fi,j 表示 p3j=i[1,i] 对答案的贡献,状态转移方程为 fi,j=maxk=3(j1)i3{fk,j1+w(k+1,i)} ,其中 w(k+1,i) 表示 [k+1,i] 的次大值。
      • Fi(x)=fx,i ,此时 Fi 可能会凹凸不平,不妨对 Fi 取前缀 max 后只维护斜率变化点处的转移。
        • 这个优化在随机数据下表现良好。
    点击查看代码
    ll a[6010],ans[6010],f[6010][6010],g[6010][6010];
    vector<ll>opt;
    ll median(ll a,ll b,ll c)
    {
    	return a+b+c-max({a,b,c})-min({a,b,c});
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("easy.in","r",stdin);
    	freopen("easy.out","w",stdout);
    #endif
    	ll n,maxx=0,i,j,k;
    	cin>>n;
    	for(i=1;i<=3*n;i++)  cin>>a[i];
    	for(i=1;i<=3*n;i++)
    	{
    		g[i+2][i]=median(a[i],a[i+1],a[i+2]);
    		maxx=max({a[i],a[i+1],a[i+2]});
    		for(j=i+3;j<=3*n;j++)
    		{
    			if(a[j]>=maxx)
    			{
    				g[j][i]=maxx;
    				maxx=a[j];
    			}
    			else  g[j][i]=max(g[j-1][i],a[j]);
    		}
    	}
    	memset(f,-0x3f,sizeof(f));
    	for(i=0;i<=n;i++)  f[0][i]=0;
    	opt.push_back(0);
    	for(j=1;j<=n;j++)
    	{
    		for(i=3*j;i<=3*n;i++)
    		{
    			for(k=0;k<opt.size()&&opt[k]<=i-3;k++)  
    				f[j][i]=max(f[j][i],f[j-1][opt[k]]+g[i][opt[k]+1]);
    			ans[j]=max(ans[j],f[j][i]);
    		}
    		opt.clear();  opt.push_back(3*j);
    		for(i=3*j+1;i<=3*n;i++)
    		{
    			if(f[j][i]>f[j][i-1])  opt.push_back(i);
    			else  f[j][i]=f[j][i-1];
    		}
    	}
    	for(i=1;i<=n;i++)  cout<<ans[i]<<" ";
    	return 0;
    }
    
  • 正解

    • 进一步挖掘对 Fi 取前缀 max 后的其他一些性质。此时能够直接从 ah(h[w+1,i]) 作为中位数的极短区间左端点转移而来,考虑单调栈预处理。
    • 当选出的 hi 相邻时会有一些 Corner Case ,添加相邻 3 个位置的转移即可。
    点击查看代码
    ll a[6010],l[6010],r[6010],f[6010][6010];
    stack<ll>s;
    vector<ll>opt[6010];
    ll median(ll a,ll b,ll c)
    {
    	return a+b+c-max({a,b,c})-min({a,b,c});
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("easy.in","r",stdin);
    	freopen("easy.out","w",stdout);
    #endif
    	ll n,i,j,k;
    	cin>>n;
    	for(i=1;i<=3*n;i++)  
    	{
    		cin>>a[i];
    		while(s.empty()==0&&a[s.top()]<=a[i])
    		{
    			if(a[s.top()]==a[i])  l[i]=s.top();
    			s.pop();
    		}
    		if(l[i]==0&&s.empty()==0)  l[i]=s.top();
    		s.push(i);
    	}
    	while(s.empty()==0)  s.pop();
    	for(i=3*n;i>=1;i--)
    	{
    		while(s.empty()==0&&a[s.top()]<=a[i])
    		{
    			if(a[s.top()]==a[i])  r[i]=s.top();
    			s.pop();
    		}
    		if(r[i]==0&&s.empty()==0)  r[i]=s.top();
    		opt[r[i]].push_back(i);
    		s.push(i);
    	}
    	memset(f,-0x3f,sizeof(f));
    	f[0][0]=0;
    	for(i=1;i<=3*n;i++)
    	{	
    		if(l[i]!=0&&l[i]<i-1)  
    			for(j=1;3*j<=i;j++)  f[i][j]=max(f[i][j],f[l[i]-1][j-1]+a[i]);
    		for(k=0;k<opt[i].size();k++)
    			if(opt[i][k]+1<i)
    				for(j=1;3*j<=i;j++)  f[i][j]=max(f[i][j],f[opt[i][k]-1][j-1]+a[opt[i][k]]);
    		if(i-3>=0)
    			for(j=1;3*j<=i;j++)  f[i][j]=max(f[i][j],f[i-3][j-1]+median(a[i-2],a[i-1],a[i]));
    		for(j=0;3*j<=i;j++)  f[i][j]=max(f[i][j],f[i-1][j]);
    	}
    	for(i=1;i<=n;i++)  cout<<f[3*n][i]<<" ";
    	return 0;
    }
    

T2 P1026. Trash Problem 40pts

  • 部分分

    • 部分分
      • 40pts
        • 枚举子矩形后 O(n2) 进行 check 。子矩形合法当且仅当 1 水平、竖直扩展的极长区间长度为偶数,且存在一种合法的覆盖方案。
        • 覆盖时考虑从左到右、从上到下枚举每个没有被覆盖的 1 ,分别向四个方向进行扩展,一旦发现无法扩展说明不存在合法方案。
          • 如果不进行判断时挂一组经典 hack
            0110
            1111
            1111
            0110
            
        • 正确性好像不是很显然。
    点击查看代码
    int up[310][310],dw[310][310],st[310][310],ed[310][310],sum[310][310],vis[310][310];
    char s[310][310];
    bool check(int x1,int y1,int x2,int y2)
    {
    	if((sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1])%4!=0)  return false;
    	for(int i=x1;i<=x2;i++)
    	{
    		for(int j=y1;j<=y2;j++)
    		{
    			vis[i][j]=0;
    			if(s[i][j]=='1'&&((min(ed[i][j],y2)-max(st[i][j],y1))%2==0
    							||(min(dw[i][j],x2)-max(up[i][j],x1))%2==0))
    				return false;
    		}
    	}
    	for(int i=x1;i<=x2;i++)
    	{
    		for(int j=y1;j<=y2;j++)
    		{
    			if(s[i][j]=='1'&&vis[i][j]==0)
    			{ 
    				if(i-1>=x1&&j-1>=y1&&s[i-1][j-1]=='1'&&vis[i-1][j-1]==0
    				&&s[i][j-1]=='1'&&vis[i][j-1]==0&&s[i-1][j]=='1'&&vis[i-1][j]==0)
    				{
    					vis[i-1][j-1]=vis[i][j-1]=vis[i-1][j]=vis[i][j]=1;
    					continue;
    				}
    				if(i+1<=x2&&j-1>=y1&&s[i+1][j-1]=='1'&&vis[i+1][j-1]==0
    				&&s[i][j-1]=='1'&&vis[i][j-1]==0&&s[i+1][j]=='1'&&vis[i+1][j]==0)
    				{
    					vis[i+1][j-1]=vis[i][j-1]=vis[i+1][j]=vis[i][j]=1;
    					continue;
    				}
    				if(i-1>=x1&&j+1<=y2&&s[i-1][j+1]=='1'&&vis[i-1][j+1]==0
    				&&s[i][j+1]=='1'&&vis[i][j+1]==0&&s[i-1][j]=='1'&&vis[i-1][j]==0)
    				{
    					vis[i-1][j+1]=vis[i][j+1]=vis[i-1][j]=vis[i][j]=1;
    					continue;
    				}
    				if(i+1<=x2&&j+1<=y2&&s[i+1][j+1]=='1'&&vis[i+1][j+1]==0
    				&&s[i][j+1]=='1'&&vis[i][j+1]==0&&s[i+1][j]=='1'&&vis[i+1][j]==0)
    				{
    					vis[i+1][j+1]=vis[i][j+1]=vis[i+1][j]=vis[i][j]=1;
    					continue;
    				}
    				return false;
    			}
    		}
    	}
    	return true;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("trash.in","r",stdin);
    	freopen("trash.out","w",stdout);
    #endif
    	int n,ans=0,i,j,x1,y1,x2,y2;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)  
    		{
    			cin>>s[i][j];
    			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(s[i][j]=='1');
    			up[i][j]=(s[i-1][j]=='1')?up[i-1][j]:i;
    			st[i][j]=(s[i][j-1]=='1')?st[i][j-1]:j;
    		}
    	}
    	for(i=n;i>=1;i--)
    	{
    		for(j=n;j>=1;j--)
    		{
    			dw[i][j]=(s[i+1][j]=='1')?dw[i+1][j]:i;
    			ed[i][j]=(s[i][j+1]=='1')?ed[i][j+1]:j;
    		}
    	}
    	for(x1=1;x1<=n;x1++)
    		for(y1=1;y1<=n;y1++)
    			for(x2=x1;x2<=n;x2++)
    				for(y2=y1;y2<=n;y2++)
    					if(check(x1,y1,x2,y2)==true)  ans++;
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 正解

    • 考虑只枚举左上边界,然后从上往下进行扩展,记录每个格子当前是 2×2 的一半还是已经完成。每次扩展的时候如果是一半且下一个是 0 ,则包含这一列的都不合法,否则将状态 1 。某个时刻合法当且仅当所有 1 的状态都是已经完成,且不包含前面提到的那些不合法的列且出现过的所有连续状态 1 的区间交的长度为偶数。
    • 前两个条件在扩展的时候不断向左移动右端点是容易处理的。需要水平方向预处理出能扩展到的最远位置。
    • 对于第三个限制条件,我们实际上可以只考虑出现在某个奇数个数位置的 1 的影响,预处理其他 1 位置选或不选的贡献,使用 bitset 加速即可,时间复杂度为 O(n4ω)
    点击查看代码
    int r[310][310];
    char s[310][310];
    bitset<310>st[310][310],zero[310][310],a,b,c,f[310];
    int solve(int x,int y,int n)
    {
    	int sum=0;
    	for(int i=y;i<=n;i++)
    	{
    		if(s[x][i]=='1')
    		{
    			sum++;
    			if(sum%2==1)  st[x][y][i-y]=1;
    		}
    		else  if(sum%2==1)  return i-2;
    		else  sum=0;
    	}
    	return (sum%2==1)?n-1:n;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("trash.in","r",stdin);
    	freopen("trash.out","w",stdout);
    #endif
    	int n,ans=0,limit,len,i,j,k;
    	cin>>n;
    	f[0][0]=1;
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)  cin>>s[i][j];
    		for(j=1;j<=n;j++)  r[i][j]=solve(i,j,n);
    		f[i]=f[i-1];  f[i][i]=1;
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			a=b=st[i][j];
    			limit=r[i][j]-j+1;
    			len=min((int)a._Find_first(),limit);
    			if(len-1>=0)  ans+=len-(b&f[len-1]).count();// 部分 1 无法自己独立选择
    			for(k=i+1;k<=n;k++)
    			{
    				limit=min(limit,r[k][j]-j+1);// 向左移动右端点
    				c=a;
    				b|=(a&st[k][j]);  a^=st[k][j];// 翻转状态
    				c&=a;
    				limit=min(limit,(int)c._Find_first());// 翻转后可能会导致不合法,需要向左移动右端点
    				len=min((int)a._Find_first(),limit);
    				if(len-1>=0)  ans+=len-(b&f[len-1]).count();
    			}
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

T3 P1027. Random Problem 10pts

  • 部分分

    • 10pts :模拟。
    点击查看代码
    const int p=998244353;
    bitset<90010>a[90010];
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("random.in","r",stdin);
    	freopen("random.out","w",stdout);
    #endif
    	int n,m,x1,y1,x2,y2,ans=0,i,j,k;
    	scanf("%d%d",&n,&m);
    	for(k=1;k<=m;k++)
    	{
    		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
    		swap(x1,y1);  swap(x2,y2);
    		if(x1>x2||y1>y2)
    		{
    			swap(x1,x2);  swap(y1,y2);
    		}
    		if(x2-x1==y2-y1)  for(i=x1,j=y1;i<=x2;i++,j++)  a[i][j]=1;
    		else  if(x1==x2)  for(i=y1;i<=y2;i++)  a[x1][i]=1;
    		else  for(i=x1;i<=x2;i++)  a[i][y1]=1;
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)  if(a[i][j]==1)  ans=(ans+1ll*i*j%p)%p;
    	}
    	printf("%d\n",ans);
    	return 0;
    }
    
  • 正解

总结

  • T1 一开始以为有决策单调性或可以进行线段树优化,然后发现假了。
  • T2 口胡出第一部分的 check 后发现死活过不了 n=20 的数据,被迫花一个多小时对每个合法子矩形进行手动 check
posted @   hzoi_Shadow  阅读(44)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2024-02-22 2024初三集训模拟测试4
2024-02-22 2024初三集训模拟测试3
扩大
缩小
点击右上角即可分享
微信分享提示