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

Codeforces Round#670(Div。2)AD問題解決策
//評価値1987/2184で記述
//補足

コンテストリンク:https://codeforces.ml/contest/1406

質問A
貪欲、実装

タイトルは、たくさんの自然数を与え、それらを2つの部分に分割できるようにすることを意味します。2つの部分のそれぞれに表示されない最小の自然数を追加し、最大の結果を要求して、最大の結果が何かを尋ねます。

貪欲になって、2つの部分を順番に実行してください。
0から始まり、連続+1は最大xをとることができます。この部分に表示されない最小の自然数はx + 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;

int num[107];//记录每个数字出现了几次

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        memset(num,0,sizeof(num));
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
        {
    
    
            int x;
            cin>>x;
            num[x]++;
        }
        int ans=0;
        for(int j=0;j<2;j++)//执行两次,分为两个部分
        {
    
    
            for(int i=0;1;i++)
            {
    
    
                if(i==101) {
    
    ans+=101;break;}//循环到101就可以结束了,输入数据最大也只有100
                if(num[i]==0) {
    
    ans+=i;break;}//当前数字不存在,则就为当前部分的最小未出现自然数,累加到答案并结束循环
                else num[i]--;//代表使用一个数字到当前部分里
            }
        }
        cout<<ans<<endl;
    }
}

質問B
貪欲、分類された議論、単純な全体的思考

質問は、一連の数値が与えられた場合、その中から任意の5つの数値を選択して乗算できることを意味します(質問は、これを理解するのが非常に簡単であることを意味します)。最大の結果が見つかるといいのですが。

ここで任意に5桁を選択します。最終結果が正であるか負であるかに応じて(どちらの場合も0を含めることができます)、最終結果の絶対値は異なります。
結果が正の場合、絶対値をできるだけ大きくします。
結果が負の場合は、絶対値をできるだけ小さくします。

5つの数値を選択すると、負の数値が出現する回数に応じて、最終結果の正と負を決定できます。
したがって、負の数0、2、4回、1、3、5回の発生を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;

vector<ll>zhengshu,fushu;

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
        {
    
    
            ll x;
            cin>>x;
            if(x<0) fushu.push_back(x);
            else zhengshu.push_back(x);
        }
        sort(fushu.begin(),fushu.end());
        sort(zhengshu.begin(),zhengshu.end());
        ll ans=-llINF;
        for(ll i=1;i<=5;i+=2)//正数或者0的出现次数为奇数,负数的出现次数为偶数
        {
    
    
            ll j=5-i;//i为正数出现次数,j为负数出现次数
            if(zhengshu.size()>=i&&fushu.size()>=j)//此时结果为正数,我们要使得绝对值尽可能大,这样值更大
            {
    
    
                ll temp=1;
                for(ll k=1;k<=i;k++) temp*=zhengshu[zhengshu.size()-k];//正数绝对值大的在末尾
                for(ll k=0;k<j;k++) temp*=fushu[k];//负数绝对值大的在开头
                ans=max(ans,temp);
            }
        }
        for(ll i=0;i<5;i+=2)//正数或者0的出现次数为偶数,负数的出现次数为奇数
        {
    
    
            ll j=5-i;
            if(zhengshu.size()>=i&&fushu.size()>=j)//此时结果为负数,我们要使得绝对值尽可能小,这样值更大
            {
    
    
                ll temp=1;
                for(ll k=0;k<i;k++) temp*=zhengshu[k];//正数绝对值小的在开头
                for(ll k=1;k<=j;k++) temp*=fushu[fushu.size()-k];//负数绝对值小的在末尾
                ans=max(ans,temp);
            }
        }
        zhengshu.clear();
        fushu.clear();
        cout<<ans<<endl;
    }
}

質問Cの
ツリー、構造、グラフの結論

質問は、n個のノードを持つツリーを提供し、このツリーからノードを取得することを意味します。これにより、残りのグラフでノードの数が最も多いツリーのノードの数ができるだけ少なくなります。
さて、このノードを複数の方法で取得したくありません。元のツリーからエッジを削除し、エッジを再接続して新しいツリーを形成し、取得したノードを一意にすることができます。

ここでは、最初に、削除されたノードに複数のオプションがある状況を紹介する必要があります。
ここでの結論は、エッジの左側と右側のノードの数がちょうどn / 2(この場合、nは偶数でなければならない)の場合、それぞれこのエッジの左側と右側のノードを取得し、残りの部分が最大のノードであるということです。数値はすべてn / 2です。

この結論を導き出すには、矛盾する方法を使用できます。残りの部分のノードの最大数がn / 2より大きいという反例は説明されていません。
残りの部分のノードの最大数がn / 2未満の場合、そのポイントが削除された後、少なくとも3つの残りの部分があり、そのうちの1つはxノードの現在の最大部分であり、他の2つの部分はそれぞれyです。そしてzノード、y <= x、z <= xおよびx + y + z = n。
取り除かれた点は、3つのパーツの接続ノードです。
したがって、この点をとらない場合、
x + y> x、x + z> x、y + z> x
は次のことを意味するため、これらの3つの部分のうち少なくとも2つが接続されてより大きな部分を形成します。他の点を取ると、xよりも大きな部分が得られます。

n / 2の両側のノード数でこのような「中間エッジ」を見つけるには、dfsを使用して取得できます。次に、エッジを削除してエッジを追加し、そのような「中間エッジ」のないツリーを作成します。
この「中間エッジ」の存在に気づきました。実際、このサブツリーは正確にn / 2ノードの2つの部分に分割でき、2つの部分には1つのエッジしかありません。
それらの間に複数のエッジが必要です。
dfsを使用してこの「中間エッジ」を見つけます。このエッジの両側にあるポイントのうち、uはルートノードに近いポイント、dはルートノードから遠いポイントです。次に、uの側からポイントを切り取り(木の根を使用)、dに接続します。
この場合、uの側にはn / 2-1のポイントとn / 2ポイントを構成するポイントdがあり、ポイントdの側には少なくとも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=1e5+7;

struct Edge
{
    
    
    ll to,next;
}edge[maxn*2];

ll head[maxn],ru[maxn],tot,n;//ru[i]数组代表i结点有几条边,用于寻找只有一条边的点作为树根起点
ll u,d;

void init()
{
    
    
    for(ll i=1;i<=n;i++) head[i]=-1,ru[i]=0;
    tot=0;
}

void add(ll u,ll v)
{
    
    
    edge[tot].to=v;
    edge[tot].next=head[u];
    ru[v]++;
    head[u]=tot++;
}

ll dfs(ll now,ll pre)//dfs返回值为now以及它的子树总共有几个结点,pre为之前的一个节点,用于dfs防止走回头路
{
    
    
    ll ret=1;
    for(ll i=head[now];i!=-1;i=edge[i].next)
    {
    
    
        ll to=edge[i].to;
        if(to!=pre) ret+=dfs(to,now);
    }
    if(ret==n/2)//如果当前结点的个数恰好等于总结点数的一半,则记录这条“中间边”两侧的两个点位置,u是更接近根节点那一侧的点
    {
    
    
        u=pre;
        d=now;
    }
    return ret;
}


int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        init();
        for(ll i=1;i<n;i++)
        {
    
    
            ll u,v;
            cin>>u>>v;
            add(u,v);add(v,u);
        }
        if(n&1)//如果总结点数是奇数个则无需进行操作,任意切掉一条边再重新把这条边构造还原即可
        {
    
    
            ll now=1;
            ll to=edge[head[now]].to;
            cout<<now<<' '<<to<<endl;
            cout<<now<<' '<<to<<endl;
        }
        else//如果结点数是偶数个,那么去找是否有哪边边的两侧恰好结点数都是总数的一半,就会出现去除该条边两侧的两个点后,得到的剩余部分的最大结点数相等的情况
        {
    
    //此时最大结点数都是n/2,我们需要构造使得这种情况不再出现
            ll start=-1;//start为树根节点,挑选只有一条边的叶子作为树根(如果选择树内部而不是叶子作为起点去dfs检测子树节点数量是否等于n/2是不正确的,因为还可能通过树根和其他子树的部分加起来构成n/2的一个子树,以另一个点为树根的情况下)
            for(ll i=1;i<=n;i++) if(ru[i]==1) start=i;
            u=d=0;//u为那条“中间边”两侧的点中离根节点较近的点,d为较远的
            dfs(start,0);//dfs检测是否存在这样一条“中间边”,左右两侧的节点数都是n/2
            ll to=edge[head[start]].to;//to为与根节点相连的唯一一个点的位置,用于后面构造操作
            if(u==0)//如果不存在这样的“中间边”则无需操作,任意切掉一条边再重新把这条边构造还原即可
            {
    
    
                cout<<start<<' '<<to<<endl;
                cout<<start<<' '<<to<<endl;
            }
            else//如果存在这样的“中间边”,从这条边的u那一侧切割下一个点,这里切割下树根节点,然后拼接到另一侧的d点上即可
            {
    
    
                cout<<start<<' '<<to<<endl;
                cout<<start<<' '<<d<<endl;
            }
        }
    }
}

質問D
貪欲、結論、違い

質問は、長さnのシーケンスAが与えられた場合、値が減少しない長さnのシーケンスBと値が増加しない長さnのシーケンスCを構築する必要があることを意味し、B [i] + C [i]を満たします。 = A [i]。次に、シーケンスBとシーケンスCの最大値の最小値を尋ねます。

シーケンスBとCの最大値にのみ注意する必要があることに注意してください。シーケンスBの最大値は最後の数であり、シーケンスCの最大値は最初の数です。
シーケンスCの最初の数はxとして固定されていると想定できます。シーケンスBの最初の数はA [1] -xです。
次に、Aiの値を順に振り返ります
。A[i] <= A [i-1]の場合、i-1の位置に従ってBとCを構築すると、合計はA [i]より大きいため、数を減らすには、シーケンスCの数を直接減らすことができます。これは、BとCの最大値に影響を与えず、うまく検索できます。
A [i]> A [i-1]の場合、i-1の位置に従ってBとCを構築しても、合計はA [i]未満になるため、数を増やす必要があります。シーケンスCの数を増やすことはできません。この値は、数列Bのみを増やすことができます。この増加は、Bの最後の数のサイズに影響します。つまり、最終的な最大値に影響します。

0より大きい部分をA [i] -A [i-1]に累積します。値はtempであり、貪欲な構成後のシーケンスBの最大値はA [1] -x + tempであり、シーケンスCです。の最大値はxであり、それらの最大値をできるだけ小さくしたい。つまり、大きい方の部分は2つの部分の加算後に2つの部分に分割され、2つの部分の加算値はA [1] -x + temp + x = A [1] + tempになります。
したがって、実際には、以下の数値の差分配列の最初の位置の数値と0より大きい部分にのみ注意を払う必要があります。
差分配列O(1)を使用して操作を変更し、結果を取得できます。

また、A [1] + tempが負の場合、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;

vector<ll>num;

int32_t main()
{
    
    
    IOS;
    int n;
    cin>>n;
    num.resize(n+1);
    for(ll i=1;i<=n;i++) cin>>num[i];
    ll ans=num[1];
    for(ll i=n;i>1;i--)//差分操作
    {
    
    
        num[i]-=num[i-1];
        if(num[i]>0) ans+=num[i];
    }
    cout<<(max(ans/2,ans-ans/2))<<endl;//注意负数除以2是向上取整而不是向下,所以这里这么写是最稳妥的
    ll q;
    cin>>q;
    for(ll i=0;i<q;i++)
    {
    
    
        ll l,r,x;
        cin>>l>>r>>x;
        if(l>1)
        {
    
    
            if(num[l]>0) ans-=num[l];
            if(num[l]+x>0) ans+=num[l]+x;
        }
        if(l==1) ans+=x;//ans是num[1]加上后序差分数组大于0的部分,如果改变区间左侧就是1的话,直接要把x加到ans上
        if(r<n)
        {
    
    
            if(num[r+1]>0) ans-=num[r+1];
            if(num[r+1]-x>0) ans+=num[r+1]-x;
        }
        num[l]+=x;
        if(r<n) num[r+1]-=x;
        cout<<max(ans/2,ans-ans/2)<<endl;
    }
}

おすすめ

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