2025-11-05 NOIP 模拟赛2 赛后总结

省流:

100+0+0+0。

T1 小 Z 的手套

题意

给定长为 \(n\) 的序列 \(a\) 和长为 \(m\) 的序列 \(b\)

你需要匹配 \(\min(n,m)\)\(a_i,b_j\),每个数只能匹配一次。

其中 \(a_i\)\(b_j\) 匹配的代价是 \(|a_i-b_j|\)。一个匹配组的代价是所有匹配的代价的 \(\max\)

最小化匹配组的最大值,求这个最大值。

赛时

想了贪心,然后发现做不了。

然后想二分,然后切了。

题解

首先,贪心是假的。因为你只有在确定代价上界的时候才能贪。

然后发现最大值最小,考虑二分。

考虑如何 check

不难注意到,排序后,当一个位置能和当前能匹配的最靠左的位置匹配时,也就是匹配后的代价不超过二分的 \(mid\),则匹配这个位置一定不劣。

而注意到,当一个位置不能被这个数匹配的时候(比当前数小),则比他大的下一个数也不能匹配这个位置。

所以可以用双指针来维护可以匹配的位置。

从 aoao 那学过来的记录程序运行时间的方法。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;

int n,m;
long long a[100010];
long long b[100010];

bool chk(long long md){
    int p2=1,cnt=0;
    for(int p1=1;p1<=n;p1++){
        while(p2<=m&&abs(a[p1]-b[p2])>md) p2++;
        if(p2==m+1) break;
        p2++;
        cnt++;
    }
    return cnt==n;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) cin>>b[i];
    sort(a+1,a+1+n);
    sort(b+1,b+1+m);

    if(n>m) swap(n,m),swap(a,b);

    long long l=0,r=2e9;
    while(l<r){
        long long mid=(l+r)>>1;
        if(chk(mid)) r=mid;
        else l=mid+1;
    }
    cout<<l<<"\n";

    # ifndef ONLINE_JUDGE
    cerr<<"\nUsed time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
    # endif
    return 0;
}

T2 小 Z 的字符串

题意

给你一个字符串,你可以任意交换相邻的两个字符。

问把这个字符串变为任意两个相邻位置的字符都不相同所需的最小操作次数。

赛时

跳了。没写。

题解

\(dp_{i,j,k,l}\) 表示考虑前 \(i\) 个位置,当前已经放了 \(j\)\(0\)\(k\)\(1\),最后一个位置是 \(l\) 时,所有字符所需的最小移动次数总和。

然后转移不算太显然。

最后看了 mhh 的记录过的。

空间可能会炸,需要滚掉第一维。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;

int n;
int a[410];

vector<int> vec[3];

int dp[2][410][410][3]; // pre i, j x 0, k x 1, last is l.

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    
    string s;cin>>s;n=s.length();
    for(int i=1;i<=n;i++) a[i]=s[i-1]-'0';

    for(int i=1;i<=n;i++) vec[a[i]].push_back(i);

    memset(dp[0],0x3f,sizeof dp[0]);

	for(int i=0;i<3;i++) dp[0][0][0][i]=0;
	for(int i=1;i<=n;i++){
		memset(dp[i&1],0x3f,sizeof(dp[i&1]));

		for(int j=0;j<=min(i,(int)vec[0].size());j++){
			for(int k=0;k<=min(i,(int)vec[1].size());k++){
				int x=i-j-k;
				if(x>(int)vec[2].size()) continue;

				if(j) dp[i&1][j][k][0]=min(dp[~i&1][j-1][k][1],dp[~i&1][j-1][k][2])+abs(i-vec[0][j-1]);
				if(k) dp[i&1][j][k][1]=min(dp[~i&1][j][k-1][0],dp[~i&1][j][k-1][2])+abs(i-vec[1][k-1]);
				if(x) dp[i&1][j][k][2]=min(dp[~i&1][j][k][0],dp[~i&1][j][k][1])+abs(i-vec[2][x-1]);
			}
		}
	}

	int ans=inf;
	for(int i=0;i<3;i++) ans=min(ans,dp[n&1][vec[0].size()][vec[1].size()][i]);
    if(ans==inf) cout<<"-1\n";
    else cout<<ans/2<<"\n";

    # ifndef ONLINE_JUDGE
    cerr<<"Used time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
    # endif
    return 0;
}

看懂了吗,上面的做法是假的。

哈哈,mhh 的做法被 hxf hack 了。

所以我的也跟着假了。

。。就这样吧,我也懒得补了。。

想看真做法可以去 JZ8 的博客

总结

吃不下去了。

这场真可石啊。。什么神人会往 T3 放线段树分治啊。。

posted @ 2025-11-05 13:23  AeeE5x  阅读(8)  评论(1)    收藏  举报