Codeforces Round #670 (Div. 2) 詳細題解
Codeforces Round #670 (Div. 2) 詳細題解
A. Subset Mex
題意
給出 \(t\) 個樣例, 每個樣例中包含一個序列長度 \(n\) 以及 對應位置的值 \(a_i\)
現將序列拆分為兩個集合 \(A\) 、\(B\), 使得 \(mex(A)+mex(B)\) 最大
其中 \(mex( 空集 )\) = \(0\) .
其中 \(mex()\) 運算得到的值為集合中 the smallest non-negative integer that doesn't exist in the set【沒有出現在集合中的最小非負整數】
思路
對 mex() 的性質可以得到,要是的 結果最大,即分給的兩個集合最小非負整數最大。
貪心的思想,每一次取使得mex最大即可(兩次遍歷)
code
#include <bits/stdc++.h> using namespace std; const int maxn = 105; int a[maxn]; int main(){ int t; cin>>t; while(t--){ int n; cin>>n; memset(a,0,sizeof(a)); for(int i=1;i<=n;i++) { int x; cin>>x; a[x]++; } int pos1,pos2; for(int i=0;i<=101;i++){ if(a[i] <= 0) { pos1 = i; break; }else a[i]--; } for(int i=0;i<=101;i++){ if(a[i] <= 0) { pos2 = i; break; } } cout<<pos1+pos2<<endl; } }
B. Maximum Product
題意
給一個長度大於 \(5\) 的序列, 使得取出序列中的 \(5\) 個不同位置的值乘積最大
思路
考慮貪心,將序列排序,正數即為從大到小取的乘積,但是負數會有影響。負數的取值是從小到達【因為絕對值大小】,且要保證取的個數儘可能為偶數(奇數還是負數)
可以採取列舉方法,在正取 \(i\)個和 逆向取 \(n-i\) 個進行組合,\(i \in 5\)的範圍,求出最值。
code
#include<bits/stdc++.h> #define IOS cin.tie(0);std::ios::sync_with_stdio(false); using namespace std; const int maxn=1e5+100; typedef long long LL; LL a[maxn],b[maxn]; bool cmp(LL a,LL b) { return a>b; } int main(){ IOS LL t;cin>>t; while(t--){ LL n;cin>>n; for(LL i=0;i<=n+10;i++) a[i]=b[i]=0; for(LL i=1;i<=n;i++) { cin>>a[i];b[i]=a[i]; } sort(a+1,a+1+n);//小到大 sort(b+1,b+1+n,cmp);//大到小 LL ans=-1e18; for(LL i=0;i<=5;i++){//拿0,1,2,3,4,5個 LL j=5-i;LL sum=1; for(LL k=1;k<=i;k++){ sum*=a[k]; } for(LL k=1;k<=j;k++){ sum*=b[k]; } ans=max(ans,sum); } cout<<ans<<endl; } return 0; }
C. Link Cut Centroids
題意
給出一棵樹,你需要通過 先任意刪除一條邊 再任意增加一條邊,使得樹的重心個數只有一個,且仍滿足為樹的形式。
【重心】:刪除該點以及相鄰邊後,所形成的最大聯通塊【最大子樹結點個數最少】最小。且一棵樹最多有兩個重心, 同時兩個重心相鄰【此題核心】
思路
如果這棵樹只有一個重心,那麼任意刪除並增加同一條邊即可
如果這棵樹有兩個重心,那麼刪除兩個重心路徑之間的任意一邊,然後增加上一邊使得其中一個刪除後連通塊更大。假設兩個重心為 \(x,y\) 同時 \(x\) 為 \(y\) 的父節點,那麼只需要 【取下\(y\)子樹上的葉子並連線到\(x\)上即可】
cut a leaf from y's subtree and link it with x. After that, x becomes the only centroid.
還有一個問題 ,如何找到重心:
在 DFS 中計算每個子樹的大小,記錄“向下”的子樹的最大大小,利用總點數 - 當前子樹(這裡的子樹指有根樹的子樹)的大小得到“向上”的子樹的大小。在該問題中將 \(1\) 作為樹的根節點即可
code
#include<bits/stdc++.h>
#define _for(i,a,b) for( int i=(a); i<(b); ++i)
#define _rep(i,a,b) for( int i=(a); i<=(b); ++i)
using namespace std;
const int maxn = 1e5+10;
vector<int>g[maxn];
int minn;
int fa[maxn],siz[maxn];
int c1,c2;
int n;
void dfs(int x,int f){
fa[x] = f,siz[x] = 1;
int mx = 0;
for(int y:g[x]){
if(y==f) continue;
dfs(y,x);
siz[x] += siz[y];
mx = max(mx,siz[y]);
}
mx = max(mx,n-siz[x]);//x最為根,上面的樹大小
if(mx<minn) minn = mx,c1 = x,c2 = 0;
else if(mx == minn) c2 = x;//第二個重心
}
int L;//葉子結點
void dfs2(int x,int f){
if(g[x].size() == 1){
L = x;
return;
}
for(int y:g[x]){
if(y==f) continue;
dfs2(y,x);
}
}
int main(){
int t;
cin>>t;
while(t--){
cin>>n;
minn = 1e9;
_rep(i,1,n) g[i].clear(),fa[i] = 0;
_for(i,1,n){
int u,v;
cin>>u>>v;
g[u].push_back(v),g[v].push_back(u);
}
dfs(1,0);
//只有一個重心
if(!c2){
//刪除 根 1 的任意一邊
cout<<"1 "<<g[1][0]<<endl;
cout<<"1 "<<g[1][0]<<endl;
}else{
//假設c1為c2的父節點
if(fa[c1] != c2) swap(c1,c2);
dfs2(c1,c2);
//刪除作為父節點的子樹葉子並連線在另一個重心上
cout<<L<<' '<<fa[L]<<endl;
cout<<L<<' '<<c2<<endl;
}
}
}
D. Three Sequences
題意
給定一個長為 \(n\) 序列, 你需要構造兩個序列 \(b\) , \(c\) 使得
- \(for\) $every $ \(i ,(1≤i≤n) b_i+c_i = a_i\)
- b is non-decreasing, which means that for every \(1<i≤n\), \(bi≥bi−1\)must hold【b是非單調遞減序列】
- c is non-increasing, which means that for every \(1<i≤n\) ,\(ci≤ci−1\)must hold【c是非單調遞增序列】
構造出 \(b\) , \(c\) 後,你需要使得 \(max(b_i,c_i)\) 最小
同時還有\(q\) 次操作,第 \(i\) 次操作使得 \([l,r]\) 增加 \(x\) ,對於每一次修改後都要輸出 \(max(b_i,c_i)\)
思路
從題意中即可得 \(ans = max(b_n,c_1)\)
對於區間修改可以使用 樹狀陣列,也可以使用差分
轉化一下數學模型可以將\(a_i\) 視為下列分佈的點 , y 軸為 \(a_i\) 的值, x 軸為序列序號
要使得 b,c 滿足單減單增,則必須滿足 \(bi>a_i\) 並且 \(b_i \leq b_{i-1}\) .
所以如下圖所示,如果採用貪心的思路,將 \(b_1 = a_1 = c_1\) ,那麼依次遞推則可以得到貪心所得解.
如果對所有的點進行一次貪心取,最有解即可得到全部解. 由於序列長 \(n\in 1e5\) 所以,這種 \(n^2\) 的方法不可取
\(a_i>a_{i-1}then\) \(b_i=b_{i−1}+a_i−a_{i−1}\) \(and\) \(c_i=c_{i−1}\)
Else if $ a_i<a_{i−1}$ \(then\) \(b_i=b_{i−1}\) \(but\) \(c_i=c_{i−1}+a_i−a{i−1}\)
所以由次規律計算 \(\sum max(0,a_i-a_{i-1})\) 得到結果假設為 \(K\) ,\(c_1\)的值假設為 \(x\),那麼 \(b_n\) 最後結果即為 \(a_1-x+K\),所以我們只需將\(max(x,a_1-x+K)\) 最小化即可
其中\(K\) 為定值,所以 \(x=a_1-x+K\) 時即為最小,兩個一次函式的交點
所以在改變時,考慮 \(a_l-a_{l-1}\) 與 \(a_r - a_{r-1}\) 即可 【差分性質】
code
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define _for(i,a,b) for(int i=(a);i<=(b);i++)
typedef long long ll;
using namespace std;
const int maxn = 1e5+5;
ll a[maxn];
ll sumg = 0,suml = 0;
int n;
void cg(int x,ll y){
if(x>n)return;
if(a[x]>0) sumg-=a[x];
a[x]+=y;
if(a[x]>0) sumg+=a[x];
}
int main(){
IOS
cin>>n;
_for(i,1,n){
cin>>a[i];
if(i>=2){
if(a[i]-a[i-1]>0) sumg += a[i]-a[i-1];
//else suml += a[i-1]-a[i];
}
}
for(int i=n;i;i--) a[i] = a[i] - a[i-1];
ll a1 = a[1];
ll ans = (ll)ceil((double)(a1+sumg)/2.0);
cout<<ans<<endl;
int q;
cin>>q;
_for(i,1,q){
int l,r;
ll x;
cin>>l>>r>>x;
if(l==1) a1+=x;
else cg(l,x);
cg(r+1,-x);
//cout<<c[l]<<' '<<c[r+1]<<endl;
ll ans = (ll)ceil((double)(a1+sumg)/2.0);
cout<<ans<<endl;
}
}
E. Deleting Numbers
題意
互動題
給出 \(n\) 表示的\(1,2,..n\)的序列並且有一個未知的 ,接下來可以進行至多 次詢問找到 ,有三種形式的詢問:
-
- :詢問當前序列有多少個數是 的倍數;
-
- :詢問當前序列有多少個數是 的倍數並將其刪除,但 永遠不會被刪除(此處 不能為 );
-
- :答案 為 。
- 資料範圍:
可以理解為猜數字了
思路
如果我們知道一個質數因子 x ,我們可 找到 \(x\) 通過暴力查詢的方法
要找到這個素數因子,我們可以通過 \(B\space p\) 升序查詢所有的質數 \(p\) ,同時計算除 \(x\) 以外的個數,如果不符合對應個數的花,則 \(x\) 含有素數因子 \(P\)
這樣我們可以找到所有素數因子除了最小的一個
假設 \(m\) 為 不超過 \(n\) 的素數個數
我們可以將其分到 \(\sqrt m\) 組
每一次詢問一個組,\(A\space 1\) 並且確認返回值與除\(x\) 以外相同
如果第一次找到不同,意味著最小的素數在這個範圍中,再確認所有在這個範圍中的素數
在找到素數因子後,對於每個因子,查詢\(A\space p^k\), 在時間複雜度 \(log(n)\) 以內
所以總的時間複雜度 \(m+2\sqrt m+log(n)\), 即為 \(O(nlogn)\)
【詳細參考官方題解】 連結