Codeforces Round #670 (Div. 2) AD problem solution

Codeforces Round #670 (Div. 2) AD problem solution
// written in rating value 1987/2184
// supplement

Contest link: https://codeforces.ml/contest/1406

Question A
Greedy, implement

The title means to give you a bunch of natural numbers and let you divide them into two parts. Add the smallest natural number that does not appear in each of the two parts, request the largest result, and ask what the largest result is.

Just be greedy and take the two parts in turn.
Starting from 0, continuous +1 can take up to x, then the smallest natural number that does not appear in this part is x+1, which is the maximum value that we can get by using the current remaining numbers.

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

Question B
Greed, categorized discussion, simple overall thinking

The question means that given a sequence of numbers, you can choose any 5 numbers from it and multiply it (the question means it is very simple to understand this), and I hope you will find the largest result.

Choose 5 digits arbitrarily here, depending on whether the final result is positive or negative (0 can be included in either case), we have different absolute values ​​of the final result.
If the result is positive, then we want the absolute value to be as large as possible.
If the result is negative, then we want the absolute value to be as small as possible.

And picking 5 numbers, we can determine the positive and negative of the final result according to the number of times the negative number appears.
Therefore, we can divide the occurrence of negative numbers 0, 2, 4 times, and 1, 3, 5 times into two cases for discussion, and take the maximum value of each case.

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

Question C
tree, structure, graph conclusion

The question means, give you a tree with n nodes, and now take a node from this tree, so that the number of nodes in the tree with the largest number of nodes in the remaining graph should be as small as possible.
Now, I don’t want this node to be taken in multiple ways. You can remove an edge from the original tree and reconnect an edge to form a new tree, making the taken node unique.

Here we must first introduce, under what circumstances will the removed node have multiple options.
The conclusion here is that if the number of nodes on the left and right sides of an edge is exactly n/2 (in this case, n must be an even number), then we take the nodes on the left and right sides of this edge respectively, and the remaining part is the largest node The numbers are all n/2.

To derive this conclusion, you can use the method of contradiction, and the counterexample that the maximum number of nodes in the remaining part is greater than n/2 is not explained.
If the maximum number of nodes in the remaining part is less than n/2, it means that after that point is taken away, there are at least three remaining parts, one of which is the current largest part with x nodes, and the other two parts with y respectively And z nodes, y<=x, z<=x and x+y+z=n.
The point taken away is the connecting node of the three parts.
So if we don’t take this point, at least two of these three parts will be connected to form a larger part,
because x+y>x, x+z>x, y+z>x
means we Taking other points will get a larger part than x, which is overturned.

To find such a "middle edge" with the number of nodes on both sides of n/2, we can get it through dfs, then how do we delete edges and add edges to construct a tree without such "middle edges".
We noticed the existence of this "middle edge", in fact, this subtree can be divided into two parts with exactly n/2 nodes, and the two parts have only one edge.
We just need to have multiple edges between them.
We use dfs to find this "middle edge". Among the points on both sides of this edge, u is the point closer to the root node, and d is the point further away from the root node. Then we can cut out a point from the side of u (use the root of the tree), and connect it to d.
In this case, there are only n/2-1 points on the side of u, plus the point d to make up n/2 points, and the point d has at least one edge on its side, which is cut off If the original root node has another connection edge with it, the above conclusion will no longer exist.

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

Question D
Greedy, conclusion, difference

The question means that given a sequence A of length n, you need to construct a sequence B of length n that does not decrease in value and a sequence C of length n that does not increase in value, satisfying B[i]+C[i] =A[i]. Now ask you what is the minimum value of the largest value in sequence B and sequence C.

Note that we only need to pay attention to the maximum value in sequence B and C, the maximum value in sequence B is the last number, and the maximum value in sequence C is the first number.
We can assume that the first number in sequence C is fixed as x, then the first number in sequence B is A[1]-x.
Then we look back at the value of Ai in turn.
If A[i]<=A[i-1], if we still construct B and C according to the position of i-1, the sum will be greater than A[i], we need To reduce the number, we can directly reduce the number of the sequence C, and it will not affect the maximum value of B and C to search well.
If A[i]>A[i-1], if we still construct B and C according to the position of i-1, the sum will be less than A[i], we need to increase the number, we cannot increase the number of the sequence C The value can only increase the number sequence B, and this increase will affect the size of the last number in B, that is, it will affect the final maximum value.

We accumulate the part greater than 0 in A[i]-A[i-1], the value is temp, then the maximum value in the sequence B after greedy construction is A[1]-x+temp, and in the sequence C The maximum value of is x, and we want the maximum value of them to be as small as possible. That is, the larger part is divided into two parts after the addition of the two, and the added value of the two is A[1]-x+temp+x=A[1]+temp.
Therefore, we actually only need to pay attention to the number in the first position and the part greater than 0 in the difference array of the following numbers.
We can modify the operation through the difference array O(1) and get the result.

Also note that when A[1]+temp is negative, dividing by 2 is rounded up.

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

Guess you like

Origin blog.csdn.net/StandNotAlone/article/details/108600583