1406C. Link Cut Centroids
题意:给一颗树,定义树的质心为:将质心及质心所连的所有边剔除之后,生成的联通块的大小的最大值最小的点。要求删除一条边,再加入一条边,使得新图仍然是树且质心唯一。
思路:通过质心的定义就能够明白树的质心至多2个,且若质心有两个,这两个质心必定是父子节点的关系。那么这个问题就很好解决了,只有两种可能:1.质心只有一个。那么只要不改变树本身的样子,即删除、添加任意的一条边,质心仍然唯一;2.质心有两个。那么只要将两个质心的其中一个质心按照剔除的规则生成的最大连通块给破坏掉就只会剩下另一个质心了,所以要特别考虑的就是如何破坏其中一个质心。可以想到,两个质心被剔除的时候生成的最大连通块都是以另一个质心为根的子树,因此只要将其中一个质心的子树的任意一个叶子结点连接到另一个质心的子树上,就算是成功破坏了这个质心了。最简单的方法当然就是再来一个DFS找到A质心的叶子结点然后和B质心连接。
以下说明代码:
声明数组:1. : 表示将 结点以及 结点所连的边删除得到新森林的最大连通块的大小。
2. : 表示以 结点为根的子树的大小。
3.: 表示 结点的子结点中 最大的结点
根据质心的定义,就可以知道ans数组的计算方法:。
那么只要再一个for循环遍历ans数组就可以知道最小的ans是否存在两个,即是否有2个质心。
AC代码:
/*---------------------------------
*File name: A.cpp
*Creation date: 2020-09-22 19:34
*Link:
*-------------------------------*/
#pragma GCC diagnostic error "-std=c++11"
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define LL long long
#define PII pair<int, int>
using namespace std;
typedef int readtype;
const int maxn = 1e5 + 5;
const int inf = INT_MAX;
const LL mod = 1e9 + 7;
inline readtype read(){
readtype X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
struct Node{
int v, nxt;
}edge[maxn << 1];
int head[maxn], tot = 0;
inline void Add_Edge(int u, int v){
edge[tot].v = v;
edge[tot].nxt = head[u];
head[u] = tot++;
}
int sz[maxn];
int son[maxn];
int pre[maxn];
int ans[maxn];
int n;
void Dfs(int u, int p = -1){
sz[u] = 1;
pre[u] = p;
for(int i = head[u]; i != -1; i = edge[i].nxt){
int v = edge[i].v;
if(v == p) continue;
Dfs(v, u);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
ans[u] = max(n - sz[u], sz[son[u]]);
//printf("%d: sz = %d, son = %d, ans = %d\n", u, sz[u], son[u], ans[u]);
}
int leaf;
void Dfs1(int u, int p){
bool flag = 0;
for(int i = head[u]; i != -1; i = edge[i].nxt){
int v = edge[i].v;
if(v == p) continue;
Dfs1(v, u);
flag = 1;
break;
}
if(!flag) leaf = u;
}
int main(){
int t = read();
while(t--){
n = read();
for(int i = 1; i <= n; ++i) head[i] = -1, son[i] = 0; tot = 0;
for(int i = 1; i < n; ++i){
int x = read(), y = read();
Add_Edge(x, y);
Add_Edge(y, x);
}
Dfs(1);
int Min = ans[1], nMin = inf, x = 1, y = -1;
for(int i = 2; i <= n; ++i){
if(Min >= ans[i]){
nMin = Min;
Min = ans[i];
y = x;
x = i;
}
}
//printf("Min = %d, nMin = %d,x = %d, y= %d\n", Min, nMin, x, y);
if(Min == nMin){
if(pre[x] == y) swap(x, y);
Dfs1(y, x);
printf("%d %d\n", leaf, pre[leaf]);
printf("%d %d\n", leaf, x);
}
else{
printf("%d %d\n", 1, edge[head[1]].v);
printf("%d %d\n", 1, edge[head[1]].v);
}
}
return 0;
}
1406D. Three Sequences
题意:给一个长度为的数组A,要求构造两个新的长度为的数组B、C,且满足以下要求:1.;2.B数组为非递减数组;3.C数组为非递增数组。对A数组将会有次修改,给出L,R,X,每次修改。要求输出修改前以及每一次修改后的max(B[i], C[i])。
思路:由于B、C数组是并非是严格单调的数组,因此可以想到,A[i] 比起 A[i - 1] 的大小变化,可以在不修改B[i - 1]或C[i - 1]的情况下就得到新的B[i], C[i],即:若当 ,那么只需要让,,反之亦然,则可以构造出BC数组,且必定满足三个要求。而要求输出的,由于BC具有单调性,实际上是输出即可。对于初始数组A,我们若只关注B数组的增加,那么可以知道和之间的差距为:,若将此表达式记为 ,那么, 将设为,那么,,因此要输出的即为:,可以看到这个表达式中只有一个变量,且Max中的两个内容为负相关。因此当时,最小,即。
那么现在考虑修改时要如何快速计算输出值:观察X的式子可以看出X仅与和相关。再根据sum的计算公式可以明白,每次对A数组的区间修改,实际上对sum的影响只有和的变化。所以每次修改A数组时只需要重新计算这两处对sum的贡献就可以得到新的X,进一步得到的值。
优化:将改变为,则对sum的影响就是 和,因此每次修改都只要更新这两处的值。总复杂度为。
AC代码:
/*---------------------------------
*File name: A.cpp
*Creation date: 2020-09-22 19:34
*Link:
*-------------------------------*/
#pragma GCC diagnostic error "-std=c++11"
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define LL long long
#define PII pair<int, int>
using namespace std;
typedef long long readtype;
const int maxn = 1e5 + 5;
const int inf = INT_MAX;
const LL mod = 1e9 + 7;
inline readtype read(){
readtype X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
vector<LL> a;
inline void Update(LL &sum, int pos, int x){
if(pos == a.size()) return;
if(pos == 1) {
a[1] += x;
return;
}
sum -= max(0LL, a[pos]);
a[pos] += x;
sum += max(0LL, a[pos]);
}
int main(){
int n = read();
a.resize(n + 1);
LL sum = 0;
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = n; i >= 2; --i) a[i] = a[i] - a[i - 1], sum += max(0LL, a[i]);
LL c = ceil((a[1] + sum) / 2.0);
LL b = a[1] - c;
printf("%lld\n", c);
int q = read();
while(q--){
int l = read(), r = read(), x = read();
Update(sum, l, x);
Update(sum, r + 1, -x);
c = ceil((a[1] + sum) / 2.0);
b = a[1] - c;
printf("%lld\n", c);
}
return 0;
}