Educational Codeforces Round 96(Rated forDiv。2)AE题解

Educational Codeforces Round 96(Rated forDiv。2)AE問題解決策
コンテストリンク:https://codeforces.com/contest/1430

質問A
単純な構造

質問は、総数がnになったことを意味し、いくつかの3、5、および7を使用して数nを形成し、任意の構築スキームを出力できるかどうかを尋ねます。

ここで、n%3の残りをとった後の結果は、検討のために3つのケースに分けられます。
n%3 = 0の場合、n / 33を直接作成します。
n%3 = 1の場合、n / 3 3を作成した後、残りは1になります。この1で7を形成するには、少なくとも2つの3が必要なので、この時点でn / 3には少なくとも2が必要です。ただ働く。
n%3 = 2の場合、n / 3 3を作成した後、残りは1になります。この2で5を形成するには、少なくとも1 3が必要なので、この時点でn / 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;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        int rest=n%3;
        if(rest==0) cout<<n/3<<' '<<0<<' '<<0<<endl;
        else if(rest==1)
        {
    
    
            if(n/3<2) cout<<-1<<endl;
            else cout<<n/3-2<<' '<<0<<' '<<1<<endl;
        }
        else
        {
    
    
            if(n/3) cout<<n/3-1<<' '<<1<<' '<<0<<endl;
            else cout<<-1<<endl;
        }
    }
}

質問B
シンプルで貪欲

質問は、n個のバケツの水があり、それぞれが最初に一定量の水を持っていることを意味します。操作ごとに1つのバケツから別のバケツに任意の量の水を注ぐことができます。k回の操作の後、最大量と最小量の水を求められます。バケット間の水の量の最大差はどれくらいですか?

ここでは、貪欲なプロセスを直接行うことができます
.1回の操作の後、1つのバケットに最大の2バケットの水を集め、水量が0の空のバケットを取得
できます.2回の操作の後、最大の3バケットの水を入れることができます。バケツに集まって、水
0の空のバケツ手に入れました...

そのため、最大のk + 1バケット(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;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n,k;
        cin>>n>>k;
        vector<ll>num(n);
        for(auto &x:num) cin>>x;
        sort(num.begin(),num.end());
        k=min(k+1,n);
        ll ans=0;
        for(ll i=1;i<=k;i++) ans+=num[n-i];
        cout<<ans<<endl;
    }
}

質問Cの
構造、貪欲、小さな結論

問題は、最初に長さnのシーケンスを、1からnまでの番号で与えることを意味します。操作ごとに現在のシーケンスで2つの番号を選択し、それらを合計して2で除算し、切り上げて元のシーケンスに戻すことができます。n-1の操作の後、シーケンス全体に残っている番号は1つだけです。
最小数を尋ねて、運用計画を作成します。

ここに小さな結論があります。
まず、2つの数値を加算して2で割ると、結果は1になります。両方の数値が1の場合のみ。
最初に持っていた番号1を使って他の番号で構成すると、現在の1が使い果たされ、1以外の番号が作成されます。1を元に戻すことはできません。
また、1を使用せず、他の番号のみで別の1を作成することも、2つの番号を取得することもできません。
したがって、最終的に得られる最小値を1、少なくとも2にすることはできません。
次に、最終的な最小値が2であることを証明します。
現在の最大の2つの数値から直接構築します。最初は、nとn-1を使用してnを取得し、次にnとn-2はn-1を取得し、n-1とn-3はn-2を取得します...最後まで3と1は2を取得します。
このようにして、nの任意のシーケンスを最終的に構築して2を取得でき、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;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        cout<<2<<endl;
        cout<<n<<' '<<n-1<<endl;
        int now=n;
        while(now>2)
        {
    
    
            cout<<now<<' '<<now-2<<endl;
            now--;
        }
    }
}

質問D
貪欲、ダブルポインター

タイトルは、0と1のみを含む長さnの文字列を指定すると、現在の文字列で毎回削除する文字を選択してから、残りの文字列で同じプレフィックスを持つすべての文字を削除し続ける必要があることを意味します。 。せいぜい何回操作できるか聞いてください。

これが直接貪欲のプロセスです。
各操作の2番目のステップでは、同じプレフィックスを持つすべての文字を削除する必要があります。つまり、すべての操作の2番目のステップでは、同じ文字の連続した間隔が削除されるため、すべての操作の最初のステップは必ず実行する必要があります。これらの間隔の数を減らしたくない場合があります。
最初のステップで削除する現在の文字数を記録する必要があり、tarは現在の位置を示します。各操作need + 1について、現在スキャンされているプレフィックスの同じ間隔の長さはLであり、最大でL-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;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int len;
        string s;
        cin>>len>>s;
        int need=0,tar=0,ans=0;//need指示当前位置后面需要删除多少个,tar为当前位置
        while(1)
        {
    
    
            ans++;
            int net=tar;
            while(net+1<len&&s[net+1]==s[tar]) net++;//找相同字符的前缀最右侧下标
            need++;//我们当前位置往后的地方在本次操作要再删一个数
            need=max(0,need-(net-tar));//当前前缀长度-1的数字,可以在前面的操作和当前的操作中贪心删掉
            if(len-net-1<=need||net==len-1) break;//剩余的部分如果比前面操作需要删除的数量多,或者已经扫到末尾了结束循环
            tar=net+1;
        }
        cout<<ans<<endl;
    }
}

質問E
貪欲、逆順ペア、バブリングモデルの類似性

質問は、小文字のみを含む文字列が与えられた場合、毎回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;
const ll maxn=2e5+7;

vector<int>origin[26];
int revese[maxn];//reverse[i]记录翻转后的第i个位置,在原来的字符串中是第几个位置
int n;
string s;

int tree[maxn];
void add(int x,int v)
{
    
    
    for(;x<=n;x+=x&-x) tree[x]+=v;
}

int sum(int x)
{
    
    
    int ret=0;
    for(;x>0;x-=x&-x) ret+=tree[x];
    return ret;
}

int32_t main()
{
    
    
    IOS;
    cin>>n>>s;
    for(int i=0;i<n;i++) origin[s[i]-'a'].push_back(i);//26个字母记录从左往右的下标
    for(int i=0;i<26;i++)
    {
    
    
        int len=origin[i].size();
        for(int j=0;j<len;j++) revese[n-origin[i][len-j-1]]=origin[i][j]+1;//同一个字母,按照翻转后所在的位置,从左往右依次贪心放进去
        //由于要用树状数组,所以这里让下标变成从1开始
    }
    ll ans=0;
    for(int i=1;i<=n;i++)//利用树状数组求个逆序对,就是冒泡从原序列变成当前序列所需的最少操作次数了
    {
    
    
        ans+=i-1-sum(revese[i]);
        add(revese[i],1);
    }
    cout<<ans<<endl;
}

おすすめ

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