Codeforces 1087 Div2(ABCDEF)

前言

这是第一次补完 div2 吧!!!!!

一、A. Flip Flops

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    ll n,c,k;
    cin>>n>>c>>k;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    priority_queue<ll,vector<ll>,greater<ll>>heap;

    for(int i=1;i<=n;i++)
    {
        heap.push(a[i]);
    }

    ll ans=c;
    while(!heap.empty())
    {
        ll cur=heap.top();
        heap.pop();

        if(cur>ans)
        {
            break;
        }

        if(cur<=ans)
        {
            ll add=ans-cur;
            cur+=min(add,k);
            k-=min(add,k);
        }

        ans+=cur;
    }
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

因为每次只能增加,所以最多就是把小于 c 的怪兽激怒到 c 再杀。所以拿个小根堆维护一下,每次模拟即可。

二、B. Array

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    for(int i=1;i<=n;i++)
    {
        int less=0;
        int cnt=n-i;
        for(int j=i+1;j<=n;j++)
        {
            if(a[j]<a[i])
            {
                less++;
            }
            else if(a[j]==a[i])
            {
                cnt--;
            }
        }
        cout<<max(less,cnt-less)<<" ";
    }
    cout<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

因为 n 不大,所以可以每次暴力。之后对于绝对值,可以考虑将其放到数轴上观察,可以发现当前数左侧和右侧只能满足一边,所以统计一下严格小于和严格大于的个数,比较即可。

三、C. Find the Zero

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

int ask(int i,int j)
{
    cout<<"? "<<i<<" "<<j<<endl;
    int res;
    cin>>res;
    return res;
}

void solve()
{
    int n;
    cin>>n;

    for(int i=1,j=1;i<=n-1;i++,j+=2)
    {
        int res=ask(j,j+1);
        if(res)
        {
            cout<<"! "<<j<<endl;
            return ;
        }
    }
    
    int res=ask(2*n,2*n-2);
    if(res)
    {
        cout<<"! "<<2*n<<endl;
        return ;
    }

    res=ask(2*n,2*n-3);
    if(res)
    {
        cout<<"! "<<2*n<<endl;
        return ;
    }
    cout<<"! "<<2*n-1<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

由于每次只能判断是否相等,且剩下的数构成一个排列,那么只有当两元素都是 0 时才会导致相等。

又因为整个序列有一半是 0,那么如果将其两两分为一组,此时若存在一组里没有 0,那么就必然有一组里都是 0。所以可以先考虑顺着两两一组问,若相等就直接返回即可。

若问到最后都没有,就说明 0 是均匀分到每一组里的。此时可以考察最后的两组,从一组里选一个元素和另一组的某个元素问。但此时可以发现,这样需要两次查询,而又因为之前用了 n 次,所以需要考虑优化。

考虑最后一组不询问,那么就节省出一次查询。之后用最后一个元素去问倒数第二组里的两元素,若相等就直接返回。而若都不相等,又因为倒数第二组里的两元素不同,那么打表可以发现,此时最后一组另一个元素必然是 0。

四、D. Ghostfires

这构史题......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int r,g,b;
    cin>>r>>g>>b;

    vector<pair<int,char>>a={{r,'R'},{g,'G'},{b,'B'}};

    sort(a.begin(),a.end());

    int add=((a[0].first+a[1].first)%2)^(a[2].first%2);

    string ans;
    while(a[0].first&&a[0].first+a[1].first+add>a[2].first)
    {
        ans+=a[0].second;
        a[0].first--;

        ans+=a[1].second;
        a[1].first--;
    }

    while(a[1].first)
    {
        ans+=a[2].second;
        a[2].first--;

        ans+=a[1].second;
        a[1].first--;
    }

    while(a[0].first)
    {
        ans+=a[2].second;
        a[2].first--;

        ans+=a[0].second;
        a[0].first--;
    }

    if(a[2].first)
    {
        ans+=a[2].second;
        a[2].first--;
    }

    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

设三种字符按个数从大到小排序后是 A,B,C,之后通过打表和观察样例可以发现,要么是 ABAB 这样,要么是 ABAC 这样,要么是 ABCBCA循环。而对于前两种,因为两个限制都只限制奇数长度后,即奇偶性和当前位置不同的位置,所以两个两个拼是一定不会违法的。

首先,考虑什么时候词频是用不完的。那么可以发现,最多就是一个 A 和一个 B 或 C 配对,之后在结尾再拼一个 A。所以设三者的个数分别为 x,y,z,那么若 x 大于 y+z+1,就说明必然用不完。而当 x 等于 y+z 或 y+z+1 时,就可以通过 ABAB......ACAC 的方式完成构造。对于 +1 的情况,就只需要判断一下 x 和 y+z 的奇偶性,若不同就需要 +1 修改成相同的奇偶性。那么当 x 小于 y+z 时,就可以通过 BCBC 这样将 y+z 的个数压到和 x 一样。

太脑电波了......

五、E. A Trivial String Problem

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n,q;
    cin>>n>>q;
    string str;
    cin>>str;
    str=" "+str;

    int l,r;
    while(q--)
    {
        cin>>l>>r;

        int m=r-l+1;
        string s=str.substr(l,m);

        vector<int>next(m+1);
        next[0]=-1;
        if(m>1)
        {
            next[1]=0;
            int i=2,cn=0;
            while(i<=m){
                if(s[i-1]==s[cn]){
                    next[i++]=++cn;
                }
                else if(cn>0){
                    cn=next[cn];
                }
                else{
                    next[i++]=0;
                }
            }
        }

        vector<int>dp(m);
        ll ans=0;
        for(int i=0;i<m;i++)
        {
            if(next[i+1]>0)
            {
                dp[i]=dp[i-next[i+1]]+dp[next[i+1]-1];
            }
            else
            {
                dp[i]=1;
            }

            ans+=dp[i];
        }
        cout<<ans<<endl;
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

注意到查询的次数很少,所以可以每次查询 O(n) 计算。

Trick:对于字符串问题,首先考虑能否 dp。

因为要求线性复杂度,所以考虑定义 为在 i 位置结尾,能划分出的最大子串个数。那么根据定义,最终答案就是

当来到每个位置 i 时,考虑其一直到前面哪个位置被划分为最后一个子串。那么对于当前要划分出去的最后一个子串,其首先必然是当前字符串的一个后缀,而且还需要是这个字符串的一个前缀串。

回顾 KMP 算法中 next 数组的定义,即不含当前字符且不包含整体字符串,前面字符串前后缀的最大匹配长度。可以发现此时最后一个子串的最长长度,其实就是 的答案,那么其就可以从 位置转移过来。之后,对于最后一部分子串,其必然有可能继续划分,可以发现这个就是 的答案了,两部分相加即可。而若 小于等于 0,那么就说明不存在这样的划分,那么此时的答案就是 1,即整个字符串。

六、F. Dynamic Values And Maximum Sum

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct TwoHeap{
public:
    TwoHeap(int _k){
        k=_k;
        up=0;
        down=0;
    }

    void push(T x){
        if(!heap1.empty()&&x<=*heap1.rbegin()){
            heap1.insert(x);
            up+=x;
        }else{
            heap2.insert(x);
            down+=x;
        }
        tidy();
    }

    void erase(T x){
        if(!heap1.empty()&&x<=*heap1.rbegin()){
            auto iter=heap1.find(x);
            if(iter!=heap1.end()){
                up-=*iter;
                heap1.erase(iter);
            }
        }else{
            auto iter=heap2.find(x);
            if(iter!=heap2.end()){
                down-=*iter;
                heap2.erase(iter);
            }
        }
        tidy();
    }
    
    //k-th value
    T top(){
        if(heap2.empty()){
            return 0;
        }
        return *heap2.begin();
    }

    void setK(int _k){
        k=_k;
        tidy();
    }

    int size(){
        return heap1.size()+heap2.size();
    }

    T query(){
        return down;
    }

private:
    multiset<T>heap1;//big root heap
    multiset<T>heap2;//small root heap
    int k;
    T up,down;

    void tidy(){
        while(!heap1.empty()&&heap2.size()<k){
            auto iter=prev(heap1.end());
            down+=*iter;
            heap2.insert(*iter);
            up-=*iter;
            heap1.erase(iter);
        }
        while(heap2.size()>k){
            auto iter=heap2.begin();
            up+=*iter;
            heap1.insert(*iter);
            down-=*iter;
            heap2.erase(iter);
        }
    }
};

void solve()
{
    int n,k;
    cin>>n>>k;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    vector<vector<int>>g(n+1);
    for(int i=1,u,v;i<=n-1;i++)
    {
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    if(k==1)
    {
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
            ans=max(ans,a[i]);
        }
        cout<<ans<<endl;
        return ;
    }

    vector<ll>best(n+1);
    vector<ll>bestFrom(n+1);
    vector<ll>choose(n+1);
    vector<ll>sec(n+1);
    vector<ll>secFrom(n+1);

    auto updateInner=[&](int u,int v)->void
    {
        if(best[v]+1>best[u]||(best[v]+1==best[u]&&bestFrom[v]<bestFrom[u]))
        {
            sec[u]=best[u];
            secFrom[u]=bestFrom[u];

            best[u]=best[v]+1;
            bestFrom[u]=bestFrom[v];
            choose[u]=v;
        }
        else if(best[v]+1>sec[u]||(best[v]+1==sec[u]&&bestFrom[v]<secFrom[u]))
        {
            sec[u]=best[v]+1;
            secFrom[u]=bestFrom[v];
        }
    };

    auto dfs1=[&](auto &&self,int u,int fa)->void
    {
        best[u]=0;
        bestFrom[u]=u;
        for(auto v:g[u])
        {
            if(v!=fa)
            {
                self(self,v,u);

                updateInner(u,v);
            }
        }
    };

    dfs1(dfs1,1,0);

    vector<ll>val(n+1);
    for(int i=2;i<=n;i++)
    {
        val[bestFrom[i]]+=a[i];
        if(bestFrom[i]!=i)
        {
            val[i]=0;
        }
    }

    TwoHeap<ll>heap(k-1);
    for(int i=1;i<=n;i++)
    {
        if(val[i])
        {
            heap.push(val[i]);
        }
    }

    vector<ll>outer(n+1);
    vector<ll>outerFrom(n+1);

    auto updateOuter=[&](int u,int v)->void
    {
        if(choose[u]!=v)
        {
            if(best[u]+1>outer[u]+1||(best[u]+1==outer[u]+1&&bestFrom[u]<outerFrom[u]))
            {
                outer[v]=best[u]+1;
                outerFrom[v]=bestFrom[u];
            }
            else
            {
                outer[v]=outer[u]+1;
                outerFrom[v]=(u==1?u:outerFrom[u]);
            }
        }
        else
        {
            if(sec[u]+1>outer[u]+1||(sec[u]+1==outer[u]+1&&secFrom[u]<outerFrom[u]))
            {
                outer[v]=sec[u]+1;
                outerFrom[v]=secFrom[u];
            }
            else
            {
                outer[v]=outer[u]+1;
                outerFrom[v]=(u==1?u:outerFrom[u]);
            }
        }
    };

    ll ans=0;

    auto dfs2=[&](auto &&self,int u,int fa)->void
    {   
        ans=max(ans,a[u]+heap.query());

        for(auto v:g[u])
        {
            if(v!=fa)
            {
                updateOuter(u,v);
                
                heap.erase(val[outerFrom[v]]);
                heap.erase(val[bestFrom[v]]);
                val[outerFrom[v]]+=a[u];
                val[bestFrom[v]]-=a[v];
                heap.push(val[outerFrom[v]]);
                heap.push(val[bestFrom[v]]);

                self(self,v,u);

                heap.erase(val[outerFrom[v]]);
                heap.erase(val[bestFrom[v]]);
                val[outerFrom[v]]-=a[u];
                val[bestFrom[v]]+=a[v];
                heap.push(val[outerFrom[v]]);
                heap.push(val[bestFrom[v]]);
            }
        }
    };

    dfs2(dfs2,1,0);

    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

好题哇!!

不难发现,在第一次操作完之后,除了叶子节点以外,其他所有点的点权都是 0 了。那么之后就只需要从所有叶子节点中选出最大的 k-1 个,将其加到总和里即可。

首先,对于动态维护前 K 大数字及其累加和,就可以通过对顶堆实现。简单说一下原理,就是用一个小根堆维护前 K 大的数字,用一个大根堆维护剩下的数字。每次添加后,关注两个堆里是否需要往另一个堆里转移数字,如果需要的话直接转移堆顶的数字即可。

之后就需要考虑第一次选哪个节点作为根能让总和最大了,那么这个就可以考虑换根 dp 了。那么在第一次 dfs 构建以 1 节点为根时的答案时,就需要求出此时每个节点的点权操作一次后会去往哪个叶子节点。那么这个就是每个节点的最大深度 best,还有对应的最深的叶子节点编号 bestFrom。之后就是构建一个 val 数组,表示以当前节点为根操作一次后,每个节点的点权,然后根据 bestFrom,对除了 1 节点以外的节点操作,然后加入对顶堆维护前 K-1 大的累加和即可。

在换根的时候,首先当前节点 v 需要从叶子节点里把点权拿回来。又因为当 u 为根时,v 去往的节点必然是自己这棵子树内最深的叶子节点,所以可以通过 bestFrom 直接查找。之后,还需要让父亲节点 u 把点权分配给最深的叶子节点。此时,由于是以 v 为根,所以 u 的子树就变成所有 v 的外部节点,包括 u 的外部节点和 v 的兄弟节点。那么因为需要让 u 的外部节点和 v 的兄弟节点决出一个最深叶子,所以在第一次 dfs 时还需要维护出 u 的最深叶子节点从哪个孩子中选出的 choose,以及次深深度 sec 和次深深度的叶子节点 secFrom。

所以之后就是每次求出 v 外部最深深度 outer 和对应的叶子节点 outerFrom,即讨论 v 是否是 u 最深的孩子,注意当 u 是 1 时外部最深叶子是 u 本身。 那么在即将去往 v 节点,讨论以 v 为根时的答案时,由于当前对顶堆维护的是以 u 为根时的前 K-1 大,所以需要进行修改。方法就是让 v 的点权回来,再让 u 的点权去往 outerFrom 即可,注意每次递归完回来要还原一下。

总结

继续努力,争取做到每次 div2 都能补完!!

END

相关推荐
Yzzz-F2 小时前
2025 ICPC武汉邀请赛 G [根号分治 容斥原理+DP]
算法
初圣魔门首席弟子2 小时前
1768. 交替合并字符串 详细题解
c++
abant22 小时前
leetcode 114 二叉树变链表
算法·leetcode·链表
tankeven2 小时前
HJ165 小红的优惠券
c++·算法
Jasmine_llq3 小时前
《B3840 [GESP202306 二级] 找素数》
开发语言·c++·试除法·顺序输入输出算法·素数判定算法·枚举遍历算法·布尔标记算法
先积累问题,再逐次解决3 小时前
快速幂优美算法
算法
低频电磁之道3 小时前
C++ 中的深浅拷贝
c++
XiYang-DING3 小时前
【LeetCode】 225.用队列实现栈
算法·leetcode·职场和发展