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)\)
【详细参考官方题解】 链接