Annoying problem
Time Limit: 16000/8000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 1127 Accepted Submission(s): 369
Problem Description
Coco has a tree, whose nodes are conveniently labeled by 1,2,…,n, which has n-1 edge,each edge has a weight. An existing set S is initially empty.
Now there are two kinds of operation:
1 x: If the node x is not in the set S, add node x to the set S
2 x: If the node x is in the set S,delete node x from the set S
Now there is a annoying problem: In order to select a set of edges from tree after each operation which makes any two nodes in set S connected. What is the minimum of the sum of the selected edges’ weight ?
Now there are two kinds of operation:
1 x: If the node x is not in the set S, add node x to the set S
2 x: If the node x is in the set S,delete node x from the set S
Now there is a annoying problem: In order to select a set of edges from tree after each operation which makes any two nodes in set S connected. What is the minimum of the sum of the selected edges’ weight ?
Input
one integer number T is described in the first line represents the group number of testcases.( T<=10 )
For each test:
The first line has 2 integer number n,q(0<n,q<=100000) describe the number of nodes and the number of operations.
The following n-1 lines each line has 3 integer number u,v,w describe that between node u and node v has an edge weight w.(1<=u,v<=n,1<=w<=100)
The following q lines each line has 2 integer number x,y describe one operation.(x=1 or 2,1<=y<=n)
For each test:
The first line has 2 integer number n,q(0<n,q<=100000) describe the number of nodes and the number of operations.
The following n-1 lines each line has 3 integer number u,v,w describe that between node u and node v has an edge weight w.(1<=u,v<=n,1<=w<=100)
The following q lines each line has 2 integer number x,y describe one operation.(x=1 or 2,1<=y<=n)
Output
Each testcase outputs a line of "Case #x:" , x starts from 1.
The next q line represents the answer to each operation.
The next q line represents the answer to each operation.
Sample Input
1 6 5 1 2 2 1 5 2 5 6 2 2 4 2 2 3 2 1 5 1 3 1 4 1 2 2 5
Sample Output
Case #1: 0 6 8 8 4
Author
FZUACM
Source
题意
- 给定一棵树以及q个询问。初始一个空的集合。两种询问,一种是往集合里添加一个点,一种是从集合里删除已经存在的点。对于每次询问,输出把集合里的点通过树的边连在一起所需要的最小代价(每条边都有权值)
- 思路
-
首先对这棵树预处理出DFS序。对集合的操作相当于构造了一棵新的树。
-
首先我们考虑插入操作。在已有的集合里寻找DFS序比操作点u大的最小点y以及小于操作点的最大点x。我们可以得到一个结论,联通这三点的路径是必须要加在新构造的树上的。(从x访问到y之间的路上不会出现集合上的点,加入u后,从x到u,u到y的路上也同样不会有集合上的点)所以我们只需要在现有的答案上加上u点到xy链的距离即可。这个距离就要通过求LCA来计算,公式
假设节点要连接到一个链中,链的定点(x,y),那么 u连接到x的距离是dfn[u] + dfn[x] - 2dfn[ lca(u,x) ] ;
u连接到y的距离dfn[u] + dfn[y] - 2dfn[ lca(u,x) ] :
x连接到y的距离dfn[x] + dfn[y] - 2dfn[ lca(x,y) ] :
u连接到x-y这个链的距离 = (u到y+u到x-x到y)/2
-
当找不到这样的两个点时,说明操作点的DFS序为最大或者最小。故我们只需要取出集合中DFS序最大和最小的两个点,同上来求即可。
-
删除时同理。注意在集合操作时,若添加则在添加前查找,删除则在删除后查找。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<time.h>
#include<string>
#define cl(a,b) memset(a,b,sizeof(a))
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define REP(i,n) for(int i=0;i<n;++i)
#define REP1(i,a,b) for(int i=a;i<=b;++i)
#define REP2(i,a,b) for(int i=a;i>=b;--i)
#define MP make_pair
#define LL long long
#define ULL unsigned long long
#define X first
#define Y second
#define MAXN 100050
using namespace std;
vector<int> e[MAXN], c[MAXN];
set<int> st;
int fa[MAXN][20];
int p[MAXN];
int dfn[MAXN];
int dis[MAXN];
int cid;
int d[MAXN];
void dfs(int cur, int f) {
dfn[++cid] = cur;
p[cur] = cid;
for (int i = 1; i < 20; ++i)
fa[cur][i] = fa[fa[cur][i - 1]][i - 1];
for (int i = 0; i < e[cur].size(); ++i) {
int u = e[cur][i];
if (u == f)
continue;
d[u] = d[cur] + 1;
dis[u] = dis[cur] + c[cur][i];
fa[u][0] = cur;
dfs(u, cur);
}
}
int lca(int u, int v) {
if (d[u] < d[v])
swap(u, v);
for (int i = 19; i >= 0; --i) {
if (d[fa[u][i]] >= d[v])
u = fa[u][i];
if (u == v)
return u;
}
for (int i = 19; i >= 0; --i) {
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
int add(int u) {
if (st.empty())
return 0;
int x, y;
set<int>::iterator it = st.lower_bound(p[u]), itx = it;
itx--;
if (it == st.end() || it == st.begin()) {
it = st.begin();
itx = st.end();
itx--;
}
y = (*it);
x = (*itx);
y = dfn[y];
x = dfn[x];
return dis[u] - dis[lca(x, u)] - dis[lca(y, u)] + dis[lca(x, y)];
}
bool bo[MAXN];
int main() {
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
int tt, ri = 0;
scanf("%d", &tt);
while (tt--) {
int n, q;
st.clear();
cid=0;
scanf("%d%d", &n, &q);
for (int i = 0; i <= n; ++i)
{
bo[i] = false;
e[i].clear();
c[i].clear();
}
for (int i = 1; i < n; ++i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
e[x].push_back(y);
c[x].push_back(z);
e[y].push_back(x);
c[y].push_back(z);
}
for (int i = 0; i < 20; ++i)
fa[1][0] = 1;
d[1] = 1;
dis[1] = 0;
dfs(1, -1);
int sum = 0;
printf("Case #%d:\n", ++ri);
for (int i = 1; i <= q; ++i) {
int x, y;
scanf("%d%d", &x, &y);
int tmp;
if (x == 1) {
if (!bo[y]){
bo[y]=true;
if (st.size() == 0) {
st.insert(p[y]);
} else {
tmp = add(y);
st.insert(p[y]);
sum += tmp;
}
}
} else {
if (bo[y]){
bo[y] = false;
st.erase(p[y]);
if (!st.empty()) {
sum -= add(y);
}
}
}
printf("%d\n", sum);
// printf("%d %d %d %d\n",now,i,sum,add(i));
}
}
return 0;
}
代码中LCA使用倍增算法。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstdlib>
#include <vector>
#include <set>
#include <map>
using namespace std;
int n,q;
const int MAXN = 100005;
int head[MAXN],cnt;
int dir[MAXN],p[MAXN][20],dep[MAXN],tme[MAXN],t;
int dfn[MAXN];
struct edge{
int u,v,w,next;
edge(){}
edge(int u,int v,int w):u(u),v(v),w(w){}
}e[MAXN<<1];
struct point{
int id;
point(){};
point(int id):id(id){}
bool operator < (const point & b) const{
return dfn[id] < dfn[b.id];
}
};
set<point> se;
void addedge(int u,int v,int w){
e[cnt] = edge(u,v,w),e[cnt].next = head[u];
head[u] = cnt++;
e[cnt] = edge(v,u,w),e[cnt].next = head[v];
head[v] = cnt++;
}
void dfs(int u){
tme[t++] = u;
for(int i = head[u];i!=-1;i = e[i].next){
int v = e[i].v;
if(!dep[v]){
dir[v] = dir[u] + e[i].w;
dep[v] = dep[u]+1;
p[v][0] = u;
dfs(v);
}
}
}
void makep(int st){
dfs(st);
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(p[i][j-1]!=-1)
p[i][j] = p[p[i][j-1]][j-1];
}
int LCA(int a,int b){
if(dep[a] < dep[b])swap(a,b);
int i;
for(i = 0; (1<<i)<=dep[a];i++);
i--;
for(int j=i;j>=0;j--)
if(dep[a]-(1<<j)>=dep[b])
a = p[a][j];
if(a == b)return a;
for(int j = i;j >= 0;j--){
if(p[a][j] != -1 && p[a][j] != p[b][j]){
a = p[a][j];
b = p[b][j];
}
}
return p[a][0];
}
int main(){
int T;
cin>>T;
for(int cas = 1;cas <= T;cas++){
se.clear();
printf("Case #%d:\n",cas);
memset(head,-1,sizeof(head));
memset(p,-1,sizeof(p));
memset(dep,0,sizeof(dep));
memset(dir,0,sizeof(dir));
cnt = t = 0;
scanf("%d%d",&n,&q);
int u,v,w;
dep[1] = 1;
for(int i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
addedge(u, v, w);
}
makep(1);
for(int i=0;i<t;i++)dfn[tme[i]] = i;
//for(int i=0;i<t;i++)printf("%d ",tme[i]);
int st = 0;
while(q--){
int op,num;
scanf("%d%d",&op,&num);
if(op == 1){
if(se.empty()){
puts("0");
se.insert(point(num));
continue;
}
if(se.find(point(num))!=se.end()){
printf("%d\n",st);
continue;
}
set<point>::iterator it = se.lower_bound(point(num));
if(it == se.begin() || it == se.end()){
int x = (se.begin())->id,y = (--se.end())->id;
st += (dir[num] - dir[LCA(x,num)]-dir[LCA(y,num)] + dir[LCA(x,y)]);
}
else{
int x = (it--)->id, y = (it)->id;
st += (dir[num] - dir[LCA(x,num)]-dir[LCA(y,num)] + dir[LCA(x,y)]);
}
se.insert(point(num);
printf("%d\n",st);
}else{
if(se.find(point(num))==se.end()){
printf("%d\n",st);
continue;
}
se.erase(point(num));
if(se.empty()){
puts("0");
se.insert(point(num));
continue;
}
set<point>::iterator it = se.lower_bound(point(num));
if(it == se.begin() || it == se.end()){
int x = (se.begin())->id,y = (--se.end())->id;
st -= (dir[num] - dir[LCA(x,num)]-dir[LCA(y,num)] + dir[LCA(x,y)]);
}
else{
int x = (it--)->id, y = (it)->id;
st -= (dir[num] - dir[LCA(x,num)]-dir[LCA(y,num)] + dir[LCA(x,y)]);
}
printf("%d\n",st);
}
}
}
return 0;
}用树状数组维护
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
using namespace std;
template <class T>
inline bool rd(T &ret) {
char c; int sgn;
if (c = getchar(), c == EOF) return 0;
while (c != '-' && (c<'0' || c>'9')) c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while (c = getchar(), c >= '0'&&c <= '9') ret = ret * 10 + (c - '0');
ret *= sgn;
return 1;
}
template <class T>
inline void pt(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) pt(x / 10);
putchar(x % 10 + '0');
}
typedef long long ll;
typedef pair<int, int> pii;
const int N = 100000 + 100;
struct Edge {
int from, to, dis, nex;
}edge[N << 1];
int head[N], edgenum;
void add(int u, int v, int d) {
Edge E = { u,v,d, head[u] };
edge[edgenum] = E;
head[u] = edgenum++;
}
int dis[N], fa[N][20], dep[N];
void bfs(int root) {
queue<int> q;
fa[root][0] = root;dep[root] = 0;dis[root] = 0;
q.push(root);
while (!q.empty()) {
int u = q.front();q.pop();
for (int i = 1;i<20;i++)fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u]; ~i;i = edge[i].nex) {
int v = edge[i].to;if (v == fa[u][0])continue;
dep[v] = dep[u] + 1;dis[v] = dis[u] + edge[i].dis;fa[v][0] = u;
q.push(v);
}
}
}
int Lca(int x, int y) {
if (dep[x]<dep[y])swap(x, y);
for (int i = 0;i<20;i++)if ((dep[x] - dep[y])&(1 << i))x = fa[x][i];
if (x == y)return x;
for (int i = 19;i >= 0;i--)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int n, q;
int c[N];
set<int>s;
inline int Lowbit(int x) { return x&(-x); }
void change(int i, int x)//i点增量为x
{
while (i <= n)
{
c[i] += x;
i += Lowbit(i);
}
}
int sum(int x) {//区间求和 [1,x]
int ans = 0;
for (int i = x; i >= 1; i -= Lowbit(i))
ans += c[i];
return ans;
}
int p[N], fp[N], top;
void dfs(int u, int fa) {
p[u] = ++top;
fp[top] = u;
for (int i(head[u]); ~i; i = edge[i].nex) {
int v = edge[i].to; if (v == fa)continue;
dfs(v, u);
}
}
int Low(int l, int r) {
int ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (sum(mid) - sum(l-1))r = mid - 1, ans = mid;
else l = mid + 1;
}
return ans;
}
int Up(int l, int r) {
int ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (sum(r) - sum(mid-1))l = mid + 1, ans = mid;
else r = mid - 1;
}
return ans;
}
int main() {
int T, Cas = 1; rd(T);
while (T--) {
memset(c, 0, sizeof c);
memset(head, -1, sizeof head); edgenum = 0;
rd(n); rd(q);
for (int i = 1, u, v, w; i < n; i++) {
rd(u); rd(v); rd(w);
add(u, v, w); add(v, u, w);
}
bfs(1);
top = 0;
dfs(1, 1);
printf("Case #%d:\n", Cas++);
s.clear();
int ans = 0;
while (q--) {
int op; int u; rd(op); rd(u);
if (op == 1) {
if (!s.count(u))
{
s.insert(u);
if (s.size() > 1)
{
int x = Up(1, p[u]), y = Low(p[u], n);
if (x == -1 || y == -1)
{
x = Low(1, n); y = Up(1, n);
}
x = fp[x]; y = fp[y];
ans += dis[u] - dis[Lca(x, u)] - dis[Lca(y, u)] + dis[Lca(x, y)];
}
change(p[u], 1);
}
}
else {
if (s.count(u))
{
s.erase(u);
change(p[u], -1);
if (s.size())
{
int x = Up(1, p[u]), y = Low(p[u], n);
if (x == -1 || y == -1)
{
x = Low(1, n); y = Up(1, n);
}
x = fp[x]; y = fp[y];
ans -= dis[u] - dis[Lca(x, u)] - dis[Lca(y, u)] + dis[Lca(x, y)];
}
}
}
pt(ans); puts("");
}
}
return 0;
}

本文探讨了一个涉及树结构的动态连通性问题,包括插入和删除节点操作后的最小连通代价计算,并介绍了如何利用DFS序、LCA算法及树状数组进行高效求解。

被折叠的 条评论
为什么被折叠?



