每次打比赛都能有一些收获,这次主要有以下几个点:
最早在看《算法竞赛入门到进阶》的时候看到了《代码规范》,我觉得很多都很好,因为这样规范不只可以让代码更统一,很多的细节的地方更可以避免不必要的问题,但是有一个点我当时不是很明白,就是“变量定义”,他建议变量在离使用最近的地方定义,我主要有两个点比较疑惑:1.把一些变量和数组定义成全局变量可以少写很多函数参数,方便很多;2.游标值有的时候可以再利用。
这次打比赛就出现了这个问题,我把游标变量i定义成的全局变量,导致我看了很久都没发现居然是这个问题错了……总之就是代码规范是长期实践总结出来的经验,按照代码规范来写可以有效地避免没有想到的bug

然后这次比赛的A题是用python过的,需要再花时间掌握分步取模(也就是CZL说的拆解)。

寒假的前半段时间主要花在了啃一些硬骨头上面,可能确实比他们慢一些,但是这样至少扎实一些,我觉得是值得的。所以我其实没有掌握太多,目前做位运算的练习稍多一些,本来想在比赛前看看并查集的,但是完全来不及了,临时补了一下矩阵快速幂也没用上。(此处感谢青竹大佬的帮助

另外这次比赛一个明显的问题就是cin/cout不敢用 因为慢。而且我G题本来TLE,加了这句话

ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);

以后直接RE了,不知道为什么

G题

https://ac.nowcoder.com/acm/contest/3002/G
其实算是我这次比赛的遗憾了,过的人数比我的排名高……
我当时的实现想法就是一个从k到n逐渐变大的滑块,不断检索。O(N^2),但是实际上会小一些,差不多是O(N^1.5)?
估计ZT他们也是差不多的思路,反正就是TLE了。
其实打字打到这里我就有了一些更多的想法,比如 检索到了就直接return 不应该单独逐次统计,这样的话每一个二级循环对应的是大约30个语句,如果实现了这个优化可能可以让二级循环每个对应的语句减少80%。
啊……我比赛的时候怎么没想到。
其实和下面的思路在实现上是差不多的:就是一边进元素一边查。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,k;
string str;
map<char,int> mp;
int main(){
    cin>>n>>k>>str;
    int ans=-1,l=0,cnt=0;
    for(int i=0;i<n;i++){
        char c=str[i];
        mp[c]++;
        while(mp[c]==k){
            if(ans==-1) ans=i-l+1;
            else ans=min(ans,i-l+1);
            mp[str[l]]--;
            l++;
        }
    }
    printf("%d\n", ans);
    return 0;
}

其实根本没用到map,时间上也不是很优秀——但就是这样本菜鸡都没想到。
它的实现逻辑就是从前往后找,找到了以后再删前面的,最后头尾减一下就是了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
char s[maxn];
vector<int> g[26];//26个容器
int main() {
    int n,k;
    cin>>n>>k>>s;
    for(int i=0; i<n; i++)
        g[s[i]-'a'].push_back(i);//统计整个字串的字母及其出现的位置
    int ans=n+1;
    for(int i=0; i<26; i++)
        if(g[i].size()>=k) 
            for(int j=k-1; j<g[i].size(); j++)
                ans=min(ans,g[i][j]-g[i][j-k+1]+1);//比最小
    if(ans!=n+1) cout<<ans<<endl;
    else puts("-1\n");
}

这个容器的写法也很漂亮。复杂度是O(n),语句数量差不多是4n左右。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 7;
typedef long long ll;
char s[maxn];
int n, k, p[30];
bool check(int x) {
    memset(p, 0, sizeof(p));
    for (int i = 1; i <= x; i++) {//这个是从原始字串的左端开始 长度为x 
        p[s[i] - 'a']++;
        if(p[s[i] - 'a'] >= k) return true;//边进边查 查到返回 
    } 
    for (int i = x + 1; i <= n; i++) {//第二个指针
    //上次没有搜到的部分 如果程序走到了这里,就说明头x个字母不满足 
        p[s[i] - 'a']++;//选取
        p[s[i-x] - 'a']--;//丢弃 
        if(p[s[i] - 'a'] >= k) return true;
    }
    return false;
}
int main() {
    cin >> n >> k;
    scanf("%s",s +1);
    int left = 1, right = n, ans = -1;
    while (left <= right) {
        int mid = left+((right-left)>>1);//这种写法比青竹大佬的写法更好
        if(check(mid)) {
            ans = mid;
            right = mid - 1;
        } else left = mid + 1;
    }
    printf("%d\n", ans);
    return 0;
}

青竹大佬的二分写法真的亮到我了。
我最先看的就是这个写法,首先在外层的长度选取上使用二分,然后在内层的已知长度求是否满足的函数里使用双指针,这样就是一个O(nlogn)的复杂度。
啊 我为什么没想到。


三月回来重做G题,wa了,忽视了多个满足k的情况的比较(也忘记了可以用双指针。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int main() {
    int n, k;
    string s;
    cin >> n >> k >> s;
    map<char, int> mp;
    int ans = -1;
    for (int R = 0, L = 0; R < n; ++R) {
        ++mp[s[R]];
        if (mp[s[R]] == k) {
            for (; L < R; L++) {
                if (s[L] == s[R]) break;
                --mp[s[L]];
            }
            if (ans == -1)
                ans = R - L + 1;
            else
                ans = min(ans, R - L + 1);
            --mp[s[L++]];//释放最左侧的位点,才能进行下一轮比较
        }
    }
    cout<<ans<<endl;
    return 0;
}

H题

https://ac.nowcoder.com/acm/contest/3002/H

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int main() {
    int n, k;
    string s;
    cin>>n>>k>>s;
    map<bool,int>cnt;
    int ans=-1;
    for(int i=0,l=0;i<n;++i){
        ++cnt[s[i]-'0'];
        while(min(cnt[0],cnt[1])==k){
            char M;
            if(cnt[0]>cnt[1])M='0';
            else M='1';
            int j=i+1;
            for(;s[j]==M&&j<n;++j) ;
            ans=max(j-l,ans);
            --cnt[s[l++]-'0'];
        }
    }
    if(ans==-1) cout<<n<<endl;
    else cout<<ans<<endl;
    return 0;
}

这个完全是我自己想出来的,其实和G很像,wa了两次才想出来要加一个后缀搜索