海亮寄 7.5

前言

业精于勤荒于嬉,行成于思毁于随

正文(模拟赛)

卦象:吉

模拟赛原题来源:KOI 2024

感受:是 IOI 赛制,\(3\) 个小时 \(7\) 道题,分数 \(431/700\)。感觉整体问题不大,当然这场模拟赛也暴露了若干短板。开始两个小时写前三道题目,顺序 \(1 \to 3 \to 2\),后面预测自己切不了 T4 且 T5 是原题,所以开 T6。发现 T6 诈骗,15 min 切掉后剩余时间观测 T7。然而 T7 于我而言是不可做题,故回去打 T4 暴力分数。短板也很明显,相对于一些代码细节的问题处理花费的时间更长(又称代码能力弱),并且打字速度并不是很快,所以还需要强化练习

题解链接

T1

人口普查题,过

T2

人口普查题 \(\times 2\),讲题时老师把这道题目升华到一个匪夷所思的高度,根本没有想到这道题目居然能和 flood-fill 扯上关系

T3

图论建模题

一开始尝试直接贪心模拟过程,最后发现不需要输出方案。于是乎考虑图论建模,观测到对于同一个连通块内,如果存在多组链式结构则无解,否则答案等于点数 + 连通块数 - 自环数

T4

神秘 DP 题

赛场上打了 \(31\) pts 的暴力,后来发现确实有一些引导作用。比如维护 L[x][y]R[x][y]f[x][y] 等。进一步地,我们考虑如何去除枚举这一复杂度

容易想到预处理,简单分讨一波

  1. V 字无关(即奇偶性不同)

    直接查询奇数组和偶数组的 \(\max \{ f \}\) 即可

  2. V 字包含

    维护 B[i][j]\((i,j)\) 所在 V 字包含的最大 \(f\)

    转移方程形如:B[i][j]=max({B[i-1][j-1],B[i-1][j],B[i-1][j+1],f[i][j]});

    答案是好贡献的

  3. V 字相交

    假设先染色靠左的 V 字,发现靠左 V 字的右翼会将网格图分割,依照题意,靠右 V 字是无法染色靠左 V 字右翼以上的部分的

    形式化地说,靠右 V 字只会贡献 \(x+y>k\) 的部分,\(k\) 是与选中的靠左 V 字有关的一个常数

    所以维护 RV[x][y] 表示 \((x,y)\) 先向右下再向右上的最大贡献

    转移方程形如:RV[i][j]=max(R[i][j],RV[i+1][j+1]+1);

    然后我们发现,我们需要对上述直线 \(x+y=k\) 维护最大的 RV 值,可以记作 pRV

    答案的来源形如确定一个 \(k\),在 \(x+y \le k\) 的部分选取一个最大的 f 值,然后在 \(x+y > k\) 的部分选取一个最大的 pRV

    前后缀最大值处理即可,时间复杂度很有保障

    对于先选右侧,再选左侧的部分是完全对称的,可以类似地定义 LVpLV,不多赘述

综上所述,我们只需要在对上述三种情况取 \(\max\) 就可以获得答案

预处理部分较多,代码实现难度中等偏上

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e3+5;
int n,m,L[N][N],R[N][N],f[N][N],B[N][N];char a[N][N];
int LV[N][N],RV[N][N],pLV[N<<1],pRV[N<<1],fLV[N<<1],fRV[N<<1];
inline void chkmx(int &x,int y){x=max(x,y);return;}
inline void chkmn(int &x,int y){x=min(x,y);return;}
inline void workl(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            L[i][j]=(a[i][j]=='0'?0:L[i-1][j-1]+1);
    return;
}
inline void workr(){
    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--)
            R[i][j]=(a[i][j]=='0'?0:R[i-1][j+1]+1);
    return;
}
inline void workf(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            f[i][j]=(a[i][j]=='0'?0:L[i][j]+R[i][j]-1);
    return;
}
inline void workb(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            B[i][j]=max({B[i-1][j-1],B[i-1][j],B[i-1][j+1],f[i][j]});
    return;
}
inline void worklv(){
    for(int i=n;i>=1;i--)
        for(int j=1;j<=m;j++)
            if(a[i][j]=='1')
                LV[i][j]=max(L[i][j],LV[i+1][j-1]+1);
    return;
}
inline void workrv(){
    for(int i=n;i>=1;i--)
        for(int j=1;j<=m;j++)
            if(a[i][j]=='1')
                RV[i][j]=max(R[i][j],RV[i+1][j+1]+1);
    return;
}
inline void work(){
    for(int i=n;i>=1;i--)
        for(int j=1;j<=m;j++){
            if(a[i][j]=='0')continue;
            chkmx(pLV[j+n-i+1],LV[i][j]);
            chkmx(pRV[i+j],RV[i][j]);
            chkmx(fLV[j+n-i+1],f[i][j]);
            chkmx(fRV[i+j],f[i][j]);
        }
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>(a[i]+1);
    workl(),workr(),workf(),workb();
    worklv(),workrv(),work();
    for(int i=1;i<=n+m;i++)chkmx(pLV[i],pLV[i-1]);
    for(int i=n+m;i>=1;i--)chkmx(pRV[i],pRV[i+1]);
    for(int i=1;i<=n+m;i++)chkmx(fRV[i],fRV[i-1]);
    for(int i=n+m;i>=1;i--)chkmx(fLV[i],fLV[i+1]);
    int odd=0,even=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if((i+j)&1)chkmx(odd,f[i][j]);
            else chkmx(even,f[i][j]);
        }
    int ans=odd+even;
    for(int i=1;i<=n+m-1;i++){
        chkmx(ans,pLV[i]+fLV[i+1]);
        chkmx(ans,fRV[i]+pRV[i+1]);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            chkmx(ans,f[i][j]+B[i-1][j]);
    cout<<ans<<'\n';
    return 0;
}

T5

原题,朴素贪心题

一个串显然是第一个出现 \(1\) 的位置所代表的后缀

贪心从高到低位贪,尽可能地满足条件,剩下的就是一些细节问题

建议结合代码食用

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
int n;char s[N],ans[N];
inline void solve(){
	cin>>n>>(s+1);
	int p=1;
	while(p<=n&&s[p]=='0')p++;
	if(p>n){cout<<"0\n";return;}
	int q=p;
	while(q<=n&&s[q]=='1')q++;
	if(q>n){
		for(int i=p;i<n;i++)cout<<(char)(s[i]);
		cout<<(p==1?"0\n":"1\n");
		return;
	}
	int x=q;
	while(x<=n&&s[x]=='0')x++;
	int len=min(x-q,q-p);
	int i=n,j=n-len;
	while(j>=q-len){
		ans[i-p+1]=(s[i]==s[j]?'0':'1');
		i--,j--;
	}
	while(i>=p){ans[i-p+1]=s[i];i--;}
	for(int i=1;i<=n-p+1;i++)cout<<(char)(ans[i]);
	cout<<'\n';
	return;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int T;cin>>T;while(T--)solve();
	return 0;
}

T6

诈骗题

显然每次查询我们只关注根节点周围的点的最小值。不妨用堆来存储当前根节点周围的所有点的编号及点权

贪心结论:将选出的子节点的所有儿子都直接加入到我们的堆里

感性理解一下,拿子节点中一个权值最小的点来代替该节点,和把所有子节点都直接链接到该点的父亲结点是等价的

因为每次必然是取最小的,再拿次小的替换,当我们把所有点都丢进去时,有堆可以保证不会先取大的数

由此,我们能明白我们的贪心是对的,接下来就是写代码了,就是优先队列的简单运用

T7

多种算法与数据结构的强大毒瘤综合题,目前还不会做

小结

讲题环节收获一般,没有听懂 T7。今天看看能不能学习一下,以后再说吧!

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-05 15:56  sunxuhetai  阅读(11)  评论(0)    收藏  举报