暑假集训CSP提高模拟3

暑假集训CSP提高模拟3


2/35 反向挂了若干分又正向挂了若干

T1 abc猜想

水,随便变形推个柿子糊个快速幂就好了

T2 简单的排列最优化题

考虑只计算每次右移的 \(delta\),我们发现一个点只会在到贡献为 \(0\) 的位置和序列开头会改变对 \(delta\) 的贡献,直接算就好,\(O(n)\)

T3 简单的线性做法题

难题,赛时写暴力套了个卡时怒艹 \(85\)

考虑两个部分分之间可以根号分治。

再考虑一个分治,直接从分治中心暴力扩展,两边扫一遍统计出可能的值(可以证明整个区间的绝对众数一定等于两边某一前(后)缀的众数)

枚举这个可能的答案,可以证明只有 \(O(\log n)\) 个,之后开个桶,记录每个右区间可以和几个左区间拼合就好了,需要前缀和优化,加上分治,复杂度 \(O(n \log^2 n)\),不过貌似有线性做法

贴个代码

#include<bits/stdc++.h>
#define llt long long
const llt N=1001000;
using namespace std;
llt n,s[N],bin[N],us[N],ans;vector<llt> vec;bool vis[N];
void solve(llt l,llt r)
{
    if(l==r)    {ans++;return;}
    vector<llt>().swap(vec);
    llt mid=l+r>>1,best=0,len;
    for(int i=mid;i>=l;i--)
    {
        bin[s[i]]++;
        if(bin[s[i]]>bin[best])  best=s[i];
        if(vis[best]==0)    vec.push_back(best),vis[best]=1;
    }
    for(int i=mid;i>=l;i--) bin[s[i]]--;
    for(int i=mid+1;i<=r;i++)
    {
        bin[s[i]]++;
        if(bin[s[i]]>bin[best])  best=s[i];
        if(vis[best]==0)    vec.push_back(best),vis[best]=1;
    }
    for(int i=mid+1;i<=r;i++) bin[s[i]]--;
    len=vec.size();
    for(int j=0;j<len;j++)
    {
        vis[vec[j]]=0;
        for(int i=mid;i>=l;i--)
            bin[s[i]]++,us[bin[vec[j]]*2-mid+i-1+mid-l+1]++;
        for(int i=(mid-l+1)*2;i>=0;i--) us[i]+=us[i+1];
        for(int i=mid;i>=l;i--) bin[s[i]]--;
        for(int i=mid+1;i<=r;i++)   bin[s[i]]++,ans+=us[(i-mid)-2*bin[vec[j]]+mid-l+1+1];
        for(int i=mid+1;i<=r;i++) bin[s[i]]--;
        for(int i=(mid-l+1)*2;i>=0;i--) us[i]=0;
    }
    solve(l,mid);solve(mid+1,r);
}
int main()
{
    #ifdef LOCAL 
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)   scanf("%lld",&s[i]);
    solve(1,n);
    printf("%lld\n",ans);
    return 0;
}

T4 简单的线性做法题

简单题,考虑每个数最多只会被开 \(6\) 次,暴力修改即可,可以用势能线段树或者并查集找到区间第一个可以修改的值# 暑假集训CSP提高模拟3


2/35 反向挂了若干分又正向挂了若干

T1 abc猜想

水,随便变形推个柿子糊个快速幂就好了

T2 简单的排列最优化题

考虑只计算每次右移的 \(delta\),我们发现一个点只会在到贡献为 \(0\) 的位置和序列开头会改变对 \(delta\) 的贡献,直接算就好,\(O(n)\)

T3 简单的线性做法题

难题,赛时写暴力套了个卡时怒艹 \(85\)

考虑两个部分分之间可以根号分治。

再考虑一个分治,直接从分治中心暴力扩展,两边扫一遍统计出可能的值(可以证明整个区间的绝对众数一定等于两边某一前(后)缀的众数)

枚举这个可能的答案,可以证明只有 \(O(\log n)\) 个,之后开个桶,记录每个右区间可以和几个左区间拼合就好了,需要前缀和优化,加上分治,复杂度 \(O(n \log^2 n)\),不过貌似有线性做法

贴个代码

#include<bits/stdc++.h>
#define llt long long
const llt N=1001000;
using namespace std;
llt n,s[N],bin[N],us[N],ans;vector<llt> vec;bool vis[N];
void solve(llt l,llt r)
{
    if(l==r)    {ans++;return;}
    vector<llt>().swap(vec);
    llt mid=l+r>>1,best=0,len;
    for(int i=mid;i>=l;i--)
    {
        bin[s[i]]++;
        if(bin[s[i]]>bin[best])  best=s[i];
        if(vis[best]==0)    vec.push_back(best),vis[best]=1;
    }
    for(int i=mid;i>=l;i--) bin[s[i]]--;
    for(int i=mid+1;i<=r;i++)
    {
        bin[s[i]]++;
        if(bin[s[i]]>bin[best])  best=s[i];
        if(vis[best]==0)    vec.push_back(best),vis[best]=1;
    }
    for(int i=mid+1;i<=r;i++) bin[s[i]]--;
    len=vec.size();
    for(int j=0;j<len;j++)
    {
        vis[vec[j]]=0;
        for(int i=mid;i>=l;i--)
            bin[s[i]]++,us[bin[vec[j]]*2-mid+i-1+mid-l+1]++;
        for(int i=(mid-l+1)*2;i>=0;i--) us[i]+=us[i+1];
        for(int i=mid;i>=l;i--) bin[s[i]]--;
        for(int i=mid+1;i<=r;i++)   bin[s[i]]++,ans+=us[(i-mid)-2*bin[vec[j]]+mid-l+1+1];
        for(int i=mid+1;i<=r;i++) bin[s[i]]--;
        for(int i=(mid-l+1)*2;i>=0;i--) us[i]=0;
    }
    solve(l,mid);solve(mid+1,r);
}
int main()
{
    #ifdef LOCAL 
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)   scanf("%lld",&s[i]);
    solve(1,n);
    printf("%lld\n",ans);
    return 0;
}

T4 简单的线性做法题

简单题,考虑每个数最多只会被开 \(6\) 次,暴力修改即可,可以用势能线段树或者并查集找到区间第一个可以修改的值,赛时被卡常了

posted @ 2024-07-24 20:56  wang54321  阅读(4)  评论(0编辑  收藏  举报