Codeforces Round#669(Div。2)AD問題解決策

Codeforces Round#669(Div。2)AD問題の解決策
//評価値1987/2184で記述
//このフィールドでACが遅い、Dが表示されなかった、評価値-46

このゲームでは、質問Aは魔法の方法について考えていて、書くのも複雑でした。合格までに16分かかりました。質問Bの通常速度は10分で、質問Cのインタラクティブな質問が意味を課しました(尋ねましたか? xyはx <yを満たしている必要があります。その後、奇妙なアルゴリズムについて考えました。waがドロップした後、質問をもう一度読んだところ、この条件なしで問題が解決したことがわかりました。書くのに約40分かかりました。Dの質問をするとき、dpのアイデアとdpの転送パスを探す方針が導入されましたが、特定の論理的な関係は明確ではなく、AとCの質問はいくらかメンタリティに影響を与えました。
貧しい状態が理由かもしれませんが、あなた自身の欠点を発見し、失敗から学ぶことができます。

コンテストリンク:https://codeforces.ml/contest/1407質問
A
分類のディスカッション、貪欲

タイトルは、0と1のみを含む長さnの偶数番号のシーケンスを提供することを意味します。この場合、最大n / 2桁を削除できるため、最終的なシーケンスでは、偶数の添字で1、奇数の添字で1になります。 1の数は同じです。

ここでの簡単な考え方は、最終的に0または1のみを含む数列を作成し、長さが偶数である場合、それは条件を満たす必要があるということです。

元のシーケンスnum0の0の出現回数と、num1の1の出現回数を記録します。
num0> = num1の場合、num1はn / 2以下でなければならず、すべて1を削除して、すべて0のシーケンスを残します。長さの偶数と奇数は要件を満たしています。
num0 <num1、num0 <n / 2の場合、すべて0を削除し、少なくとも1回は削除する可能性があります。このとき、すべて1のシーケンスがあり、条件を満たすためには長さが偶数でなければならないため、num1が奇数の場合は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 num0=0,num1=0;
        for(int i=0;i<n;i++)
        {
    
    
            int x;
            cin>>x;
            if(x) num1++;
            else num0++;
        }
        if(num0>=num1)//如果0的个数大于等于1的个数,代表1的个数小于等于n/2,删光1直接输出全0数列即可
        {
    
    
            cout<<num0<<endl;
            for(ll i=0;i<num0;i++)
            {
    
    
                if(i) cout<<' ';
                cout<<0;
            }
        }
        else
        {
    
    
            if(num1&1) num1--;//如果1的个数大于0的个数,0的个数小于n/2,删光0,如果1的个数是奇数,则再删一个1,变为全1数列
            cout<<num1<<endl;
            for(ll i=0;i<num1;i++)
            {
    
    
                if(i) cout<<' ';
                cout<<1;
            }
        }
        cout<<endl;
    }
}

質問B:
構造、欲、暴力

質問は、n個の数値が与えられた場合、数値のシーケンスによって生成されるc []配列の最大の辞書式順序を満たすために、それらを再配置する必要があることを意味します。c [i]は、配列された配列の最初のi桁のgcdに等しくなります。条件を満たすさまざまな構造シーケンスのいずれかを出力すれば十分です。

ここで、nの値は比較的小さいので、現在構築されているi番号のgcdをtempとして記録し、残りの未使用の番号の中でtempのgcdの最大値を貪欲に見つけます。
tempでgcdの複数の最大値が存在する可能性があることがわかりますが、これらの数値の順序は影響を受けないと推定し続けることができます。
gcd(a、b)= cなので、cはaおよびb以下でなければなりません。
gcd(temp、x)の最大値がyであると仮定すると、この時点で対応する複数のxがあり、それらのいずれかを選択し、tempがyになり、次にこの時点でgcd(y、x)の最大値を探します最大gcdはまだyであり、満足する数は、gcdの最後の検索の数(temp、x)です。したがって、これらの数値は任意に選択できます。

この考え方に従ってください。

#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;

bool flag[1007];//flag[i]记录i这个数字是否已经被使用

int gcd(int a,int b)
{
    
    
    return b?gcd(b,a%b):a;
}

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        vector<int>num(n);
        for(auto &x:num) cin>>x;
        memset(flag,0,sizeof(flag));
        vector<int>ans;//ans为我们最后构造的数列
        int tar=0;
        for(int i=0;i<n;i++)//找到最大的那个数字,放在最后构造数列的第一个位置
        {
    
    
            if(num[i]>num[tar])
            {
    
    
                tar=i;
            }
        }
        ans.push_back(num[tar]);
        flag[tar]=1;

        int temp=num[tar];//temp记录当前构造的ans数组中最后一个位置的值
        for(int i=1;i<n;i++)
        {
    
    
            int Max=0,tar=-1;//tar记录剩下未被使用的数字中,与temp的gcd值最大tar为多少
            for(int j=0;j<n;j++)
            {
    
    
                if(!flag[j])
                {
    
    
                    if(gcd(temp,num[j])>Max)//此处的tar有多个,取任意一个均可,原因见题解
                    {
    
    
                        Max=gcd(temp,num[j]);
                        tar=j;
                    }
                }
            }
            ans.push_back(num[tar]);
            flag[tar]=1;
            temp=gcd(temp,num[tar]);
        }

        for(int i=0;i<ans.size();i++)
        {
    
    
            if(i) cout<<' ';
            cout<<ans[i];
        }
        cout<<endl;
    }
}

質問C
相互作用、実装、要約の結論

質問は、長さnの順列num []があることを意味します。各相互作用で2つの添え字xとyを要求でき、num [x]%num [y]の結果が返されます。ここで、最大2n回の相互作用で各添え字位置の特定の値を取得する必要があります。

まず、特定の添え字位置の値を決定するために使用できるクエリの種類について考える必要があります。
num [x] <num [y]の場合、返される結果はnum [x]ですが、重要なのは、質問したときに、num [x]とnum [y]のどちらが大きいかわからなかったことです。
それでは、num [x]%num [y]を要求し、xyを交換してから、num [y]%num [x]の値をループするとどうなるでしょうか。

num [x]%num [y] = c、num [y]%num [x] = dと
すると、num [x] <num [y]の場合を考慮します(num [x]> num [y]の場合も同じです)理由、これ以上の詳細はありません)
現時点ではc = num [x]であり、dはnum [x]未満でなければならないため、c> dです。
つまり、2つのクエリの数が多いほどnum [x]とnum [y]の数が少なくなります。この場合はnum [x]で、xはcを取得したクエリですしたがって、2つのクエリを通じてnum [x]とnum [y]の小さい方を決定し、対応する添え字でそれを決定できます。

これから、tarを使用して現在の未決定の最大添え字を記録し、それを直接直接求めて、[tar、i]と[i、tar]全体を照会し、num [i]とnum [tar]の小さい方を決定して、更新します。 tar添え字、ただ適用する。

#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=1e4+7;

int num[maxn];
int n;

int32_t main()
{
    
    
    IOS;
    cin>>n;
    int tar=1;//tar指示当前扫过部分的最大值的下标,for循环时该位置的值是不确定的
    for(int i=2;i<=n;i++)
    {
    
    
        int x,y;
        cout<<"? "<<tar<<' '<<i<<endl;cout.flush();
        cin>>x;
        cout<<"? "<<i<<' '<<tar<<endl;cout.flush();
        cin>>y;
        if(x>y)//代表x是num[tar]和num[i]中较小的那个,并且x=num[tar]
        {
    
    
            num[tar]=x;//确定下标tar的值为x
            tar=i;//更新不确定的最大值位置下标为i
        }
        else num[i]=y;//num[tar]>num[i]时,不需要更新tar,直接确定下标i的值为y
    }
    num[tar]=n;//for结束后还剩下最后一个tar位置'不确定',但是他是这n排列中最大的,自然只能是n了
    cout<<"! ";
    for(int i=1;i<=n;i++)
    {
    
    
        if(i>1) cout<<' ';
        cout<<num[i];
    }
    cout<<endl;
}

質問D
dp、dp転送パスを前処理する必要があり、リレーショナルロジックを最初に導出する必要がある

タイトルは、高層ビルがn棟あり、最初の建物からn番目の建物まで続いていることを意味します。隣り合う高層ビルに移動したり、右側の高層ビルに一度にジャンプして、このセクションの中央にある高層ビルの高さに合わせることができます。高さはすべて、始点と終点の高さよりも高いか低いです。必要な操作の最小数を尋ねます。

この質問はdpを使用して転送することを考えるのは簡単です。暴力的なdpは間違いなく十分ではなく、質問が意図する4つの状況のすべての転送パスを処理できるかどうかを考えてから、dpを考えます。
競技中にここではっきりしなかったのはこの状況です。この種のデータの場合:
4 1 2 3 5
4は直接2または3にジャンプでき、すべて中央部分の高さが開始点と終了点の高さよりも低いことを満足しています。はい、効率的に対処する方法を理解できませんでした。

しかし、実際には、4は2の左側にある2以上の最初の数値であり、3の左側の3以上の最初の数値でもあります。

ここでは、Forward for方向のスタック処理を使用して、現在の位置の左側にある現在の位置以上の最初の添え字を取得し、次に逆方向のスタック処理を使用して、現在の位置の右側にある現在の位置以上の最初の添え字を取得できます。位置の添え字には、すべての遷移を含めることができ、隣接する位置に移動する遷移も含めることができます。(ここでは、理解し理解するために上記のデータと組み合わせています)

dp転送パスを前処理した後、これらのパスに従って転送します...ここで、私のコードは公式の問題解決のように各場所の転送場所の数を前処理しませんが、上記の4つの前処理された条件を直接使用します4つのアレイが転送されます。

#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=3e5+7;

ll n;
ll h[maxn];
ll dp[maxn];//dp[i]记录从下标0位置开始到下标i位置最少需要多少次跳跃
ll r_higher[maxn];//r_higher[i]记录在下标i的右侧,第一个不小于h[i]的下标位置,指示dp转移方向i到r_higher[i]
ll r_lower[maxn];//r_lower[i]记录在下标i的右侧,第一个不大于h[i]的下标位置,指示dp转移方向i到r_lower[i]
ll l_higher[maxn];//l_higher[i]记录在下标i的左侧,第一个不小于h[i]的下标位置,指示dp转移方向从l_higher[i]到i
ll l_lower[maxn];//l_lower[i]记录在下标i的左侧,第一个不大于h[i]的下标位置,指示dp转移方向从l_lower[i]到i

int32_t main()
{
    
    
    IOS;
    cin>>n;
    for(ll i=0;i<n;i++)
    {
    
    
        dp[i]=r_higher[i]=r_lower[i]=l_higher[i]=l_lower[i]=llINF;//初始化状态代表不存在
        cin>>h[i];
    }
    stack<ll>S;//定义一个栈实现我们后面分四种情况记录dp转移路径的过程
    //只需要能够理解其中一种,就能理解其他三种
    for(ll i=0;i<n;i++)//求l_higher[],也就是左侧的第一个不小于h[i]的位置下标
    {
    
    
        while(S.size()&&h[S.top()]<h[i]) S.pop();//这里我们把栈中记录的小于h[i]的位置全部弹出
        if(S.size()) l_higher[i]=S.top();//如果栈中还有剩余,那么栈顶的位置就是最近的一个位置下标
        S.push(i);//把当前位置推入栈中,在栈中已存在的下标中不存在比当前的h[i]更小的h值(关键)
    }

    while(S.size()) S.pop();
    for(ll i=0;i<n;i++)
    {
    
    
        while(S.size()&&h[S.top()]>h[i]) S.pop();
        if(S.size()) l_lower[i]=S.top();
        S.push(i);
    }

    while(S.size()) S.pop();
    for(ll i=n-1;i>=0;i--)
    {
    
    
        while(S.size()&&h[S.top()]<h[i]) S.pop();
        if(S.size()) r_higher[i]=S.top();
        S.push(i);
    }

    while(S.size()) S.pop();
    for(ll i=n-1;i>=0;i--)
    {
    
    
        while(S.size()&&h[S.top()]>h[i]) S.pop();
        if(S.size()) r_lower[i]=S.top();
        S.push(i);
    }

    dp[0]=0;
    for(ll i=0;i<n;i++)
    {
    
    
        if(l_lower[i]!=llINF) dp[i]=min(dp[i],dp[l_lower[i]]+1);//这里dp转移的时候一定要先转移l_的两个部分
        if(l_higher[i]!=llINF) dp[i]=min(dp[i],dp[l_higher[i]]+1);//因为是更前面的位置转移到当前位置,要比下面两种r_过程的当前位置转移到更后面要优先
        if(r_lower[i]!=llINF) dp[r_lower[i]]=min(dp[r_lower[i]],dp[i]+1);
        if(r_higher[i]!=llINF) dp[r_higher[i]]=min(dp[r_higher[i]],dp[i]+1);
    }

    cout<<dp[n-1]<<endl;
}

おすすめ

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