Educational Codeforces Round 95 (Rated for Div. 2)A-D题解

Educational Codeforces Round 95 (Rated for Div. 2)A-D题解
//写于rating值1987/2184
//这场unrated…很迷的一场。
//看完A的题面,内心:“这不傻逼题么”,一看样例:“???????”。“难道是我读错题意了???”,回去又看了两遍题意,然后官方开始发公告。大概在11min的时候修改了A的样例和评测数据。
//13分钟时过掉了A,再13分钟过掉了B和C,看了D一半的题面后官方发了公告说unrated,光速赶在门禁前飞回寝室…
//D的结论以及用set和优先队列实现的思路都已经推导出来,但是set的一些函数操作并不熟悉,趁此机会学习了一波

A题
水题

题意为一开始你有1根木棒,使用1根木棒和1份燃油可以做1个火把,现在你希望做k个火把。你可以进行任意次交易,每次交易你可以选择以下两种交易方式:
1.用1根木棒交换x根木棒
2.用y根木棒交换1份燃油
现在问你最少需要几次交易可以制作出k个火把。

制作k个火把需要至少k根木棒和k份燃油。
首先我们手上是没有燃油的,且燃油只能用木棒交换获得,那么k份燃油就需要至少进行k次第二种交易用k × \times ×y根木棒交换获得,总共就至少需要k × \times × y + kスティック。最初の取引方法は、トランザクションごとにx-1スティックを追加することと見なすことができます。したがって、最初のタイプに必要なトランザクションの最小数は(k×\ times× y + k)/(x-1)は切り上げられ、さらに2番目のタイプのトランザクションに必要なk回が答えになります。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        ll x,y,k;
        cin>>x>>y>>k;
        ll need=k*y+k;
        ll ans=(need-1)/(x-1)+k;
        if((need-1)%(x-1)) ans++;
        cout<<ans<<endl;
    }
}

質問B
貪欲

問題は、長さnの数列が与えられた場合、一部の位置がロックされ、これらの位置の数値の位置を変更できず、他の位置がロックされず、これらの位置の数値の位置を自由に交換できることを意味します。
ここで、このシーケンスのロックされていない位置の番号を並べ替えて、このシーケンスの接頭辞と配列内の負の数の最大の添え字が最小になるようにしてください。構築方法を出力します。

ここでは、接頭辞と配列の負の数の最大の添え字が最小であるため、特定の固定添え字の接頭辞の合計については、前のセクションで作成された数ができるだけ大きいことを期待する必要があります。この貪欲は、各固定添え字で同じです。したがって、ロックされていない位置にあるすべての番号を降順で直接再配置できます。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

vector<int>num;
int n;
int cas[107];

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        num.clear();
        num.resize(n);
        deque<int>Q;//用于存储不锁定位置上的数字,排序后进行后续构造操作
        for(int i=0;i<n;i++) cin>>num[i];
        for(int i=0;i<n;i++)
        {
    
    
            cin>>cas[i];
            if(!cas[i]) Q.push_back(num[i]);
        }
        sort(Q.begin(),Q.end());
        for(int i=0;i<n;i++)
        {
    
    
            if(!cas[i])//对不锁定位置,我们依次按照从大到小的顺序把所有数字放进去。
            {
    
    
                num[i]=Q[Q.size()-1];
                Q.pop_back();
            }
        }
        for(int i=0;i<n;i++) cout<<num[i]<<' ';
        cout<<endl;
    }
}

質問C
貪欲、全体論的思考

タイトルはn人のボスが順番に殺されることを意味し、一部は単純なボスで一部は難しいボスです。あなたとあなたの友人は交互に上司を殺します(最初に友人)。
毎回1人または2人のボスを殺すことを選択できますが、あなたの友人は難しい上司を殺すことはできません。難しい上司を殺す必要があるとき、彼はそれを使用する必要があります。キルできるアイテムは1つだけです。今、あなたはあなたとあなたの友人のための最良の戦略、あなたの友人が小道具を使う最小回数を見つける必要があります。

友達が最初の一歩を踏み出すので、最初のボスは友達に殺されなければならず、最初が難しいボスの場合、友達は一度アイテムを使用しなければなりません。それ以降の質問は、直接見ることができます。
x人の連続した難しいボスの場合、最初にイニシアチブを取ると、貪欲な戦略を採用します。毎回2人を殺し、友達は毎回1人殺します。次に、これらのx人のボスを殺す必要があります。アイテムの最小数はx / 3です。
これらのn-1(最初を除く)ボスは、連続する困難なボスの数と見なすことができます。それらの間に1つ以上の単純なボスがあります。いくつかの戦略を使用して、連続する各困難なボスをすべて作成できますか?あなたは一人で殺し始めましたか?
あなたとあなたの友達が1人のボスしか殺せない場合、友達は奇数のボスしか殺せず、あなたは偶数のボスしか殺せないので修正されますが、あなたとあなたの友達は殺すことを選択できることに注意してください2人のボス、2人のボスを殺すことを選択すると、自分と友達に対応するパリティインデックスが交換されます。隣接するセグメントの連続する難しいボス間の単純なボスを通じて、2人のボスを殺し、2人の奇数と偶数の添え字を交換して、次の難しいボスが自分で殺されるようにすることができます。

必要なプロップの最小数は最初のボスを除いてn-1ボスであると結論付けることができます。連続する難しいボスの数はそれぞれ3で除算されて累積されます。最初のボスが難しい場合はさらに1を追加します。 。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=2e5+7;

ll num[maxn];
ll n;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        for(ll i=0;i<n;i++) cin>>num[i];
        ll ans=0;
        if(num[0]) ans++;//由于朋友是先手,因此第一个boss如果就是困难的那就必须要使用一次道具
        ll temp=0;
        for(ll i=1;i<n;i++)
        {
    
    
            while(i<n&&num[i]==1)//统计连续的困难boss有多少个
            {
    
    
                temp++;
                i++;
            }
            ans+=temp/3;//除3加到答案上并置零
            temp=0;
        }
        cout<<ans<<endl;
    }
}

質問D
貪欲、セット、優先キューは、隣接するポイント間のラインセグメントの最大長を維持します。

質問は、x軸上の整数ポイントの数にゴミがあることを意味します。このゴミを2ポイント以下に集める必要があります。各操作の添え字xを選択し、位置xのすべてのガベージを位置x-1またはx + 1に移動できます。最小限の操作を出力する必要があります。この質問には、初期のガベージ状況を変更するq個の操作があります。変更ごとにガベージが追加または削除されます。変更するたびに、最小数の操作を出力する必要があります。

結論として、すべてのゴミを1か所に集めた場合、最適な操作は、ゴミの右端の位置と左端の位置との間の距離になります。ファイナルギャザリングポイントとしてこのラインセグメント上のポイントを任意に選択するため、左のパーツは左端から右に移動し、右のパーツは右端から左に移動します。これが最適なソリューションです。線分の長さに等しい。ファイナルギャザリングポイントをラインセグメントの外側の位置で選択した場合は、それを独自の原稿用紙に描画して、さらに操作が必要であることを確認します(高校数学の基本はカバーされません)。
これで2つの点に集まることができるので、隣接するゴミのペアの間の線分から元の線分を削除でき、残りの2つの線分は別々に集約され、削除されたものを作成するだけで済みます。ラインセグメントの最大長が最適なソリューションです。

このようにして、問題は、動的ポイントの隣接するラインセグメント長の最大値を維持する方法に変換されます。これは、ここで設定キューと優先度キューを使用することで実現されます。ここでの考え方は、ラインセグメントの最大値に注意を払うだけでよいので、優先度キューを使用してそれを達成できますが、プライオリティキューは値の削除をサポートしていません。削除するラインセグメントの長さを保存するには、別の優先度キューを開くだけです。できる。
元の画像に点を挿入および削除する操作では、元の画像に新しく追加された点の左側と右側にゴミが存在するかどうか、およびそれらの位置がどこにあるかを知るだけで、どの線分を削除して追加する必要があるかを知ることができます。

特定の実装については、コードを参照してください。

#include<bits/stdc++.h>
#define ll long long
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;

int n,q;
set<ll>S;//集合S用于存储当前有哪些位置上有垃圾,用于计算实现下面两个优先队列的当前线段和待删除线段
priority_queue<ll>length_now,length_delete;
//两个优先队列,now存储当前的图上所有的相邻点之间的线段长度,delete为插入和删除点过程中产生的待删除线段

//由于我们只关注所有点的最左侧和最右侧的距离(set可计算),以及相邻点之间的线段长度中的最大值(优先队列的top()可以获得)

void insert(ll x)//增加点操作
{
    
    
    if(S.size())//增加点可能会造成线段的增加和减少
    {
    
    
        auto now=S.lower_bound(x);//用lower_bound找到集合S中大于x的第一个位置
        if(now==S.begin()) length_now.push(*now-x);//如果大于x的第一个位置就是最小的点了,那加入的点是在目前的最左侧,只增加了*now-x这一条线段
        else//如果大于x的第一个位置不是最小的点
        {
    
    
            if(now==S.end())//如果大于x的第一个位置是集合的末尾,代表加入的点在最右侧,对now--操作变成之前最大点的位置,只增加了x-*now这一条线段
            {
    
    
                now--;
                length_now.push(x-*now);
            }
            else//如果不是末尾,则代表新加入的点是在两个点的中间
            {
    
    
                ll temp=*now;//用temp临时记录右侧点的值,再对now--变为左侧点的位置
                now--;
                length_now.push(temp-x);//新增加了两条线段,分别与左右侧相邻点构成
                length_now.push(x-*now);
                length_delete.push(temp-*now);//原本左右侧相邻点构成的线段被删除
            }
        }
    }
    S.insert(x);
}

void erase(ll x)//同insert操作的思路
{
    
    
    S.erase(x);
    if(S.size())
    {
    
    
        auto now=S.lower_bound(x);
        if(now==S.begin()) length_delete.push(*now-x);
        else
        {
    
    
            if(now!=S.end())
            {
    
    
                ll temp=*now;
                now--;
                length_now.push(temp-*now);
                length_delete.push(temp-x);
                length_delete.push(x-*now);
            }
            else
            {
    
    
                now--;
                length_delete.push(x-*now);
            }
        }
    }
}

void out()
{
    
    
    if(S.size()==0) cout<<0<<endl;//这里要特判一下集合S是否为空,否则下面的rbegin()-begin()的操作会出错
    else
    {
    
    
        while(length_delete.size()&&length_delete.top()==length_now.top())//我们只关注所有线段中最大的值,因此用优先队列存储即可
        {
    
    //把待删除的最大线段依次删去即可
            length_delete.pop();
            length_now.pop();
        }
        ll ans=*S.rbegin()-*S.begin();
        if(length_now.size()) cout<<ans-length_now.top()<<endl;
        else cout<<0<<endl;
    }
}

int32_t main()
{
    
    
    IOS;
    cin>>n>>q;
    for(ll i=0;i<n;i++)
    {
    
    
        ll x;
        cin>>x;
        insert(x);
    }
    out();
    for(ll i=0;i<q;i++)
    {
    
    
        ll ope,x;
        cin>>ope>>x;
        if(ope) insert(x);
        else erase(x);
        out();
    }
}

おすすめ

転載: blog.csdn.net/StandNotAlone/article/details/108614043