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