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

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

//評価値を書き込む1987/2184

コンテストリンク:https://codeforces.com/contest/1420質問
A、
並べ替え関連、思考水に関する質問

タイトルは、指定された長さnの列数を意味し、n ×\回で尋ねることができます×(n-1)/ 2-1交換回数後、値に従ってシーケンスがソートされます。


シーケンスの元の添え字に基づいて並べ替えアルゴリズムを考えようとする人がいる理由がよくわかりません。最悪の場合、必要な交換の数は1からn-1まで累積され、値はn ×\です。回×(n-1)/ 2であり、質問で与えられる操作の最大数は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;
        vector<int>num(n);
        for(auto &x:num) cin>>x;
        bool flag=1;
        for(int i=1;i<n;i++) if(num[i]>=num[i-1]) flag=0;
        if(flag) cout<<"NO"<<endl;
        else cout<<"YES"<<endl;
    }
}

Bタイトル
操作、簡単な結論

この質問は、番号シーケンスnum []が与えられた場合、i <jを満たす添え字iとjのグループの数とnum [i]&num [j]> = num [i] ^ num [j]を尋ねることを意味します。

最初に認識すべきことは、&と操作は、操作に含まれる2つの値よりも大きい値を取得しないということです。数値xの場合、x = 6を例にとると、6のバイナリ表現は000…000110です。左側のすべての0について、別の数値での&AND演算の後に1を取得することはできませんが、 ^排他的OR演算の場合、別の番号のバイナリの対応するビットが1の場合、^排他的OR演算はビット1を取得します。この場合、^演算の結果は必然的に&AND演算の結果よりも大きくなるため、左側側のすべての0も、バイナリではすべて0である必要があります。
x = 6の最上位1の場合、別の番号の対応するビットが1の場合、&および演算の結果は1になり、^排他的OR演算の結果は0になります。これは、要件を満たす必要があります。の。別の番号に対応するビットが0の場合、&および操作の結果は0になり、^ XOR操作の結果は1になります。これは、要件を満たしてはなりません。

このことから、数値num [j]の場合、タイトルの要件を満たす添え字iを形成でき、num [i]の最上位ビット1とnum [j]の最上位ビット1を満たす必要があるという結論を導き出すことができます。したがって、配列cas [i]を直接使用して、最上位ビットがi番目のビットである番号が添え字jの前に出現する回数を記録します。
答えは、cas配列を使用してO(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;

ll cas[100],sum[100];

int32_t main()
{
    
    
    IOS;
    cas[0]=1;
    for(ll i=1;;i++)
    {
    
    
        cas[i]=cas[i-1]*2;
        if(cas[i]>1e9) break;
    }
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        vector<ll>num(n);
        for(auto &x:num) cin>>x;
        memset(sum,0,sizeof(sum));
        ll ans=0;
        for(int i=0;i<n;i++)
        {
    
    
            int tar=0;
            while(cas[tar]<=num[i]) tar++;
            ans+=sum[tar-1];
            sum[tar-1]++;
        }
        cout<<ans<<endl;
    }
}

Cの質問
C1は、dpが書き込まれることを簡単に確認できますが、C2は配列データを交換および変更する必要があるため、dpがO(n)に変更されるたびに、必然的にタイムアウトになります。C2は結論を出す必要があります。

この質問は、長さnのシーケンスが与えられた場合、新しいシーケンスを形成するために添え字の一部を選択する必要があることを意味します。新しいシーケンスの値は、新しいシーケンスのすべての奇数の添え字からすべての偶数の添え字を引いたものとして定義されます。次に、最大値を尋ねます。
C2の場合、元のシーケンスに対してさらにq個の変更操作があります。変更ごとに元のシーケンスの2桁の位置が交換されます。変更ごとに、形成可能なシーケンスの最大値が出力されます。

C1の場合、dpですばやく解決できることがわかります。dp [i] [0]は、i番目の桁が使用され、新しいシーケンスの最後の桁が奇数の添え字である場合に、最初のi桁が構成できる最大値を表します。dp[i] [1]はi番目を表します。新しい番号シーケンスの最後の番号が偶数の添え字である場合に、最初のi桁が構成できる最大値。
伝達方程式では、dp [i] [0]は、i番目の桁の選択と非選択の2つの遷移に分割されます。i番目の桁が選択されていない場合、dp [i] [0]はdp [i-1] [から始まります。 0]が転送され、i番目の桁が選択されている場合、dp [i] [0]はdp [i-1] [1] + num [i]から転送され、2つが最大と見なされます。dp [i] [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=3e5+7;

ll dp[maxn][2];
ll num[maxn];
ll n,q;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n>>q;
        for(ll i=0;i<n;i++)
        {
    
    
            cin>>num[i];
            dp[i][0]=dp[i][1]=-llINF;
        }
        dp[0][0]=num[0];dp[0][1]=0;
        for(ll i=1;i<n;i++)
        {
    
    
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]+num[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]-num[i]);
        }
        cout<<max(dp[n-1][0],dp[n-1][1])<<endl;
    }
}

C2の場合、q個のスワップ操作があります。これは、シーケンス内の2つの数値の位置をスワップし、各スワップ操作の後に最大値を出力する必要があります。現時点では、C1のdpソリューションを引き続き使用するとタイムアウトになることは明らかであるため、変更するたびにO(1)またはO(logn)のソリューションを検討する必要があります。
ここでの言葉は、原則として要約され、要約されています。最終的に最適な選択は、このシリーズの中央値の「ピーク」と「バレー」である必要があります。方法を証明できれば、1、4、3、2、7などの例を自分で示すことができます。 4、3、および2の間隔では、矛盾によって4と2を選択する必要があることを証明します。この証明スキームを使用して結論を​​証明すると、シーケンスの最初と最後の「トラフ」は使用されません。
添え字iとjの数を変更および交換するたびに、影響を与える可能性のある位置(山と谷の生成または消失)は、i-1、i、i + 1、j-1、j、j + 1、6つの位置です。各位置の元の山と谷の値に対して逆計算を実行し、変更後に形成された山と谷を再計算します。ここでは、iとjが等しい場合は直接スキップすることに注意し、i +1とj-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=3e5+7;

ll num[maxn];
ll n,q;

ll check(ll i)//返回下标i的位置是波峰还是波谷,决定了这个位置上的数字是增加还是减少到最终结果上
//1代表波峰,-1代表波谷,0代表其他情况,恰好对应了其累加到结果上对应的系数
{
    
    
    if(n==1) return 1;//特判数列只有1个数的情况
    if(i==1)//如果当前位置是开头位置,如果是波峰的话就累加到结果上,其他情况都不取(开头的波谷不取)
    {
    
    
        if(num[1]>num[2]) return 1;//此时判断右侧即可
        else return 0;
    }
    if(i==n)//如果当前位置是末尾位置,如果是波峰的话就累加到结果上,其他情况都不取(末尾的波谷不取)
    {
    
    
        if(num[i]>num[i-1]) return 1;//此时判断左侧即可
        else return 0;
    }
    if(num[i]>num[i-1]&&num[i]>num[i+1]) return 1;//其他情况下如果既大于左边又大于右边则为波峰
    if(num[i]<num[i-1]&&num[i]<num[i+1]) return -1;//其他情况下入股既小于左右又小于右边则为波谷
    return 0;
}

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n>>q;
        for(ll i=1;i<=n;i++) cin>>num[i];
        ll ans=0;
        for(ll i=1;i<=n;i++)//直接for一遍,每个数乘以其check对应的系数累加到结果上即可
        {
    
    
            ans+=check(i)*num[i];
        }
        cout<<ans<<endl;
        for(ll i=0;i<q;i++)//q次调换num[l]和num[r]
        {
    
    
            ll l,r;
            cin>>l>>r;
            if(l!=r)//l=r的情况直接跳过
            {
    
    
                if(l>1) ans-=check(l-1)*num[l-1];//l-1的位置如果存在,做逆运算
                if(l<n&&l+1!=r) ans-=check(l+1)*num[l+1];//l+1的位置如果存在,也做逆运算
                //下面同样的对r-1,r+1,l,r四个位置做逆运算
                if(r>1&&r-1>l+1) ans-=check(r-1)*num[r-1];//此处要注意特判r-1的位置与l+1不重合,否则会重复运算,l和r差值小于2的情况下都会有重合
                if(r<n) ans-=check(r+1)*num[r+1];
                ans-=check(l)*num[l];
                ans-=check(r)*num[r];
                swap(num[l],num[r]);//交换后把对应受影响位置的值加回来
                if(l>1) ans+=check(l-1)*num[l-1];
                if(l<n&&l+1!=r) ans+=check(l+1)*num[l+1];
                if(r>1&&r-1>l+1) ans+=check(r-1)*num[r-1];
                if(r<n) ans+=check(r+1)*num[r+1];
                ans+=check(l)*num[l];
                ans+=check(r)*num[r];
            }
            cout<<ans<<endl;
        }
    }
}

質問D
スキャンラインのアイデアの適用ですが、これは一次元です。

タイトルは、n個のライトがあり、各ライトには開始時刻と終了時刻があることを意味します。次に、少数のk個のライトの組み合わせについて質問する必要があり、それらはすべて特定の瞬間にオンになります。 。

この質問は、実際にはラインセグメントツリースキャンラインのアイデアの単純なアプリケーションです。開始時刻と終了時刻を同じ配列に入れ、開始時刻を1としてマークし、終了時刻を-1としてマークするだけです。時間の順序に従って、直接1回。1とマークされた時間の場合、現在の間隔で同時に点灯できるライトの総数は+1であり、時間とマークされた場合は1減少します。num配列を使用して、現在の間隔で同時にオンにできるライトの数を記録します。num> = kの場合、要件を満たすk個のライトの組み合わせを形成できることを意味します。dpと同様の考え方を使用します。追加したばかりのライトは、このk個のライトの組み合わせでは、以前に計算された部分と重複することはなく、num-1からk-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 mod=998244353;
const ll maxn=3e5+7;

ll n,k;
struct Node
{
    
    
    ll data,ope;
};

vector<Node>node;

bool cmp(Node a,Node b)
{
    
    
    if(a.data==b.data) return a.ope>b.ope;//注意这里的cmp,时间点相同的时候,要把增加的点放到减少的点前面
    else return a.data<b.data;
};

ll cas[maxn];

ll qpow(ll a,ll p)
{
    
    
    ll ret=1;
    while(p)
    {
    
    
        if(p&1) ret=ret*a%mod;
        a=a*a%mod;
        p>>=1;
    }
    return ret;
}

ll cal(ll num)
{
    
    
    if(num<k) return 0;
    return cas[num-1]*qpow(cas[num-k]*cas[k-1]%mod,mod-2)%mod;//计算在num-1个数中取k-1个的组合数
}

int32_t main()
{
    
    
    IOS;
    cas[0]=cas[1]=1;
    for(ll i=2;i<maxn;i++) cas[i]=cas[i-1]*i%mod;
    cin>>n>>k;
    n<<=1;
    for(ll i=0;i<n;i++)
    {
    
    
        ll x;
        cin>>x;
        node.push_back({
    
    x,(i&1)?-1:1});
    }
    sort(node.begin(),node.end(),cmp);
    ll ans=0,num=0;
    for(ll i=0;i<n;i++)
    {
    
    
        if(node[i].ope==1)
        {
    
    
            num++;
            ans=(ans+cal(num))%mod;
        }
        else num--;
    }
    cout<<ans<<endl;
}

おすすめ

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