天梯选拔赛1补题_2024_03_05

补题1:找除数

题意:

做法:质因数分解+埃氏筛法。要注意的是埃氏筛法要筛的范围是到sqrt(1e8)=1e4;质因数分解之后,各个因子相互组合即是答案个数。

const int maxn=1e4;         //要多大。。
bool pri0[maxn+10];
vector<int> pri;
int n,ans=0;
void getpri(){
    //for(int i=2;i<=maxn/i;i++){
    for(int i=2;i<=maxn;i++){               //筛的时候要筛出1e5之内的所有质数,而不是maxn/i之内的!!!!
        if(!pri0[i]){
            pri.emplace_back(i);
            for(int j=i*i;j<=maxn;j+=i){
                pri0[j]=1;
            }
        }
    }
}
void solve(){       //I 找除数--质因数分解+埃氏筛法
    cin>>n;
    ans=0;
    unordered_map<int,int> cnt;
    for(auto p:pri){
        if(p*p>n) break;
        while(n%p==0){
            cnt[p]++;
            n/=p;
        }
    }
    if(n>1) cnt[n]++;
    for(auto c:cnt) ans+=c.second+c.second*ans;
    cout<<ans+1<<endl;
}

补题2:相加余三(hard)

题意:

做法:区间dp板子;可以定义dp[i][j]的含义为保留[i,j]区间之后的时候,取得的最大贡献是多少。容易得出状态转移有三种

if(i-2>=1) dp[i][j]=max(dp[i][j],dp[i-2][j]+(arr[i-2]+arr[i-1])%3);
if(j+2<=n) dp[i][j]=max(dp[i][j],dp[i][j+2]+(arr[j+2]+arr[j+1])%3);
if(i-1>=1&&j+1<=n) dp[i][j]=max(dp[i][j],dp[i-1][j+1]+(arr[i-1]+arr[j+1])%3);

值得注意的是不合法的i,j取值是要跳过的

if(i>j) break;
//如果n是偶数,check=1;否则为0.因为每次都要选两个,i,j的奇偶性是肯定不变的.
if(check&&(j-i+1)%2!=0) continue;
if(!check&&(j-i+1)%2==0) continue;

 还有注意!下面这一行是表达什么都不保留的状态,如果剩下最后两个,无论如何,一定是可以选择的.

if(i+1==j) dp[i][j]+=(arr[i]+arr[j])%3;   //放在最后更新,不然会被覆盖 

 

int dp[2005][2005];    //定义dp[i][j]为,l,r为i,j时,已经取得的最大 ---
void solve(){           //H 相加余3(hard)--dp--区间dp
    int n,ans=0,arr[2005];
    cin>>n;
    for(int i=1;i<=n;i++) cin>>arr[i];
    //怎么考虑dp的顺序,结果最后存在哪里--时刻更新ans
    //l=1~n r=n~1
    //dp表填表
    bool check;
    if(n%2==0) check=true;
    else check=false;
    for(int i=1;i<=n;i++){
        for(int j=n;j>=1;j--){
            if(i>j) break;
            if(check&&(j-i+1)%2!=0) continue;
            if(!check&&(j-i+1)%2==0) continue;
            if(i-2>=1) dp[i][j]=max(dp[i][j],dp[i-2][j]+(arr[i-2]+arr[i-1])%3);
            if(j+2<=n) dp[i][j]=max(dp[i][j],dp[i][j+2]+(arr[j+2]+arr[j+1])%3);
            if(i-1>=1&&j+1<=n) dp[i][j]=max(dp[i][j],dp[i-1][j+1]+(arr[i-1]+arr[j+1])%3);

            if(i+1==j) dp[i][j]+=(arr[i]+arr[j])%3;   //放在最后更新,不然会被覆盖 

            ans=max(ans,dp[i][j]);
        }
    }
    cout<<ans;
}

补题3:C-可怕的冻雨

做法:思维排序+维护STL.upper_bound是线性结构.set是红黑树,在set中要二分,用set.upper_bound(val),set自带的二分才行,不然TLE.

把冰按光滑度升序排序,把鞋子按防滑度也升序排序。

先处理防滑度小的鞋子,那么不会后面防滑度跟高的鞋子。

对于每一个鞋子,在set中插入该鞋子可以站立的冰的位置(set一开始先插入1和n两个点)。

用multiset维护set中的点与点之间的距离,即每次set插入一个点x(初始化只要插入n-1),都要删除点x前后两个点的距离,并且添加x-front,和back-x的距离.

更新直到没有冰加入set。

判断一下此时鞋子的舒适度是否大于等于dis.rbgein(),即是否大于等于最大的间隔.最后答案统一输出.

typedef struct myshoes{
    int f,s;
    int idx;
}myshoes;
myshoes shoes[100005];
int n,m;
vector<pair<int,int>> ans;
set<int> st;
multiset<int> dis;
pair<int,int> ice[100005];
bool cmp1(pair<int,int> a,pair<int,int> b){
    if(a.first!=b.first) return a.first<b.first;
    return a.second<b.second;
}
bool cmp2(myshoes a,myshoes b){
    return a.f<b.f;
}
void solve(){           //补C 可怕的冻雨-思维--!!排序!!+维护STL   好一个题
    //按冰的光滑度从小到大排序;雪地靴按防滑度从小到大排序
    //把当前防滑度>=光滑度的冰加入到set中,并且把set中两个冰之间的距离加入到multiset中,并且删除原来的距离,加入两个新的距离
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>ice[i].first;
        ice[i].second=i;
    }
    for(int i=1;i<=m;i++) {
        cin>>shoes[i].f>>shoes[i].s;
        shoes[i].idx=i;
    }
    sort(ice+1,ice+n+1,cmp1);
    sort(shoes+1,shoes+m+1,cmp2);
    int curidx=1;
    st.insert(1),st.insert(n);  //先插入始末
    dis.insert(n-1);
    for(int i=1;i<=m;i++){
        while(curidx<=n&&shoes[i].f>=ice[curidx].first){
            if(ice[curidx].second==1||ice[curidx].second==n){
                curidx++;
                continue;
            }
            /********************************************************************************/
            //auto back=upper_bound(st.begin(),st.end(),ice[curidx].second);   TLE!!!!!改了两个小时。。。以后不能这样调用了。
            auto back=st.upper_bound(ice[curidx].second);
            /********************************************************************************/
            auto front=prev(back);           //prev(&)
            dis.erase(dis.find(*back-*front));
            dis.insert(ice[curidx].second-*front);
            dis.insert(*back-ice[curidx].second);
            st.insert(ice[curidx].second);       //后面才插入
            curidx++;
        }
        if(shoes[i].s>=*dis.rbegin()) ans.emplace_back(shoes[i].idx,1);         //*dis.rbgein();
        else ans.emplace_back(shoes[i].idx,0);
    }
    sort(ans.begin(),ans.end(),cmp1);
    for(auto a:ans) cout<<a.second<<endl;
}

补题4:加一余二

题意:

做法:跟上一题可怕的冻雨非常相似,也是把点加入set,然后用multiset维护点与点之间的长度,对于每次更新完之后,输出mst.rebegin()即是最大长度.

这题因为一开始手算的时候,虽然思路是完全没错的。但是想法不够缜密,导致一开始写的代码漏洞百出。甚至连输出都没有(因为mst.erase(find(val))的值有问题,导致类似段错误),更不用说跑过样例。

只能说维护的时候,全是细节,细节中的细节。想错一点点,或者有一点点没留意到,就会出错。

void solve(){               //补F 加一余二--高人指点说类似上一题--补完上一题,再看这题,有想法。先不看题解自己写。
    // --改了一天..一发过。一开始样例都跑不出来,程序没有东西输出,以为mst.erase(find)有问题,find没找到值,然后还erase,导致报错
    //然后debug,一行一行看着过,然后手算结果,和程序对比。再对着手算结果一点一点修改程序。
    //思路是完全没错的,但是111,011和110三种情况的st,mst维护操作错了。
    // 想的不够仔细,具体upper_bound(val)填的值,和prev(val,x)填的值,以及st的erase,insert和mst的erase和insert,真的要想的非常非常仔细。
    //一点一点对着手算(前提是手算不能有错!!一开始写的几个手算全是错的,想的片面了,导致upper_bound,prev,erase,insert填的值都有错)!!
    //手算非常重要,手算想错了,代码按着手算的错想法,导致代码也出错
    string str;
    cin>>str;
    int n=str.size();
    set<int> st;
    st.insert(0);
    st.insert(n);
    for(int i=1;i<n;i++){
        if(str[i]!=str[i-1]) st.insert(i);
    }
    multiset<int> mst;
    for(auto p=st.begin();next(p)!=st.end();p=next(p)){
        mst.insert(*(next(p))-*p);
    }
    int q;
    cin>>q;
    if(n==1){
        while(q--) cout<<"1 ";
        return;
    }
    while(q--){
        int x;
        cin>>x;
        x--;            //题目输入下标从1开始,我的下标从0开始
        if(str[x]=='1') str[x]='0';
        else str[x]='1';
        if(x==0){                   //特别处理最左边和最右边
            if(str[x]==str[x+1]){           //改后相同
                int back=*st.upper_bound(1);
                st.erase(1);
                mst.erase(mst.find(1));
                mst.erase(mst.find(back-1));
                mst.insert(back-0);
            }
            else{                               //改后不同
                int back=*st.upper_bound(1);
                mst.erase(mst.find(back-0));
                mst.insert(back-1);
                mst.insert(1-0);
                st.insert(1);
            }
        }
        else if(x==n-1){
            if(str[x]==str[x-1]){           //改后相同
                st.erase(n-1);              //先删
                int front=*prev(st.upper_bound(n-1));
                mst.erase(mst.find((n-1)-front));
                mst.erase(mst.find(n-(n-1)));
                mst.insert(n-front);
            }
            else{                       //改后不同
                int front=*prev(st.upper_bound(n-1));
                mst.erase(mst.find(n-front));
                mst.insert(n-(n-1));
                mst.insert((n-1)-front);
                st.insert(n-1);
            }
        }
        else{               //普遍位置
            if(str[x]!=str[x-1]&&str[x]!=str[x+1]){             //010
                int front=*prev(st.upper_bound(x));
                int back=*st.upper_bound(x);
                st.insert(x),st.insert(x+1);
                mst.erase(mst.find(back-front));                      //find没找到值,导致出现问题
                mst.insert(x-front);
                mst.insert(back-(x+1));
                mst.insert(1);          //单独出来的那个
            }
            else if(str[x]==str[x-1]&&str[x]==str[x+1]){        //111--修改过
                int front=*prev(st.upper_bound(x),2);
                int back=*st.upper_bound(x+1);
                st.erase(x),st.erase(x+1);
                mst.erase(mst.find(1));
                mst.erase(mst.find(x-front));
                mst.erase(mst.find(back-(x+1)));
                mst.insert(back-front);
            }
            else if(str[x]==str[x-1]&&str[x]!=str[x+1]){        //110--修改过
                int front=*prev(st.upper_bound(x),2);
                int back=*st.upper_bound(x);
                st.erase(x),st.insert(x+1);
                mst.erase(mst.find(x-front));
                mst.erase(mst.find(back-x));
                mst.insert((x+1)-front);
                mst.insert(back-(x+1));
            }
            else if(str[x]!=str[x-1]&&str[x]==str[x+1]){        //011--修改过
                st.erase(x+1),st.insert(x);
                int front=*prev(st.upper_bound(x),2);    //应该是前两个
                int back=*st.upper_bound(x);
                mst.erase(mst.find(x+1-front));
                mst.erase(mst.find(back-(x+1)));
                mst.insert(x-front);
                mst.insert(back-x);
            }
        }
        cout<<*mst.rbegin()<<" ";
    }
//multiset<int> mst;
//mst.insert(1);
//mst.erase(mst.find(1));
//cout<<"yes";
}

部分手算

总结:这场比赛有两题都是二进制枚举,这种枚举可以保证各种组合都出现.不会漏组合情况. "可怕的冻雨"和"加一余二"完全是同一个类型的题目,都是set+multiset维护数值。但是加一余二的细节多很多很多,需要更加更加注意各种情况维护的时候,哪个才是正确的back和front,以及插入删除操作的哪个才是正确的值,以及值的正确关系。

posted @ 2024-03-14 00:49  osir  阅读(0)  评论(0编辑  收藏  举报