Codeforces Round 1000 (Div. 2)


A. Minimal Coprime

题意:互素区间是指gcd(l,r)=1的区间,极小互素区间是互素区间并且没有一个被他包含的区间也是互素区间。问你区间[l,r]里有多少个极小互素区间。

根据数论的基础知识,x,x+1一定是互素的,所以统计所有长度为2的区间就行,不过要注意,[1,1]是唯一一个长度不为2的极小互素区间。

点击查看代码
void solve() {
    int l, r;
    std::cin >> l >> r;
    int ans = r - l;
    if (l == 1) {
    	ans = std::max(ans, 1);
    }

    std::cout << ans << "\n";
}

B. Subsequence Update

题意:给你一个数组,你可以翻转一个子序列,求一次操作后[l,r]区间的最小总和。

赛时把子序列看成子数组了。。。
左边的最小的rl+1个数一定能翻转到[l,r]里。具体操作是,假设前rl+1小的数有x个不在[l,r]里面,那么[l,r]里有(rl+1)x个最小值,发现不是最小值的正好也是rl+1(rl+1)x)=x个,那么明显可以选中这些数进行翻转,这样x个数就到[l,r]里了。右边同理,所以两边模拟一下取最小就行。

点击查看代码
void solve() {
    int n, l, r;
    std::cin >> n >> l >> r;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    auto b = a;
    std::sort(a.begin(), a.begin() + r);
    i64 sum1 = 0, sum2 = 0;
    for (int i = 0; i < r - l + 1; ++ i) {
    	sum1 += a[i];
    }

    std::sort(b.begin() + l - 1, b.end());
    for (int i = l - 1; i < l - 1 + r - l + 1; ++ i) {
    	sum2 += b[i];
    }

    i64 ans = std::min(sum1, sum2);

    std::cout << ans << "\n";
}

C. Remove Exactly Two

题意:给你一颗树,你要删掉两个点,然后使得剩下的连通块最大。

u的度数为degu。因为树没有环,那么如果删一个点就会分成degu块,那就是删度数最大的。现在考虑两个点,发现如果删除的两个点之间没有连边的话是不影响的,如果连边则因为有一条边重复了会少分成一个块。所以我们先枚举所有度数最大的点看有没有两个最大点之间没有连边,有点话就选这两个点就行。否则就拿一个度数最大的和一个剩下点度数最大的点就行。然后我是用并查集数的联通块,不过好像可以直接算。
这题赛时代码写的奇丑。

点击查看代码
struct DSU {
    std::vector<int> f, siz;
    
    DSU() {}
    DSU(int n) {
        init(n);
    }
    
    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }
    
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

void solve() {
    int n;
    std::cin >> n;
    std::vector<std::vector<int> > adj(n);
    std::vector<std::pair<int, int> > edges;
    std::vector<int> deg(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    	++ deg[u]; ++ deg[v];
    	edges.push_back({u, v});
    }

    if (n == 2) {
    	std::cout << 0 << "\n";
    	return;
    }
    int max = *std::max_element(deg.begin(), deg.end());

    std::vector<int> b;
    int x = -1, y = -1, mx = 0;
    for (int i = 0; i < n; ++ i) {
		if (deg[i] == max) {
			b.push_back(i);
		}
    }


    if (b.size() >= 2) {
    	int mx = 0;
    	for (auto & u : b) {
    		for (auto & v : b) {
    			if (u != v) {
    				int flag = 0;
    				for (auto & x : adj[u]) {
    					if (x == v) {
    						flag = 1;
    						break;
    					}
    				}

    				if (max * 2 - flag > mx) {
    					mx = max * 2 - flag;
    					x = u, y = v;
    				}
    			}
    		}

    		if (mx == 2 * max) {
    			break;
    		}
    	}
    } else {
    	x = b[0];
    	int mx = -1;
    	for (int i = 0; i < n; ++ i) {
			if (i == x) {
				continue;
			}
			int flag = 0;
    		for (auto & j : adj[i]) {
    			if (x == j) {
					flag = 1;
					break;
				}
    		}

    		if (max + deg[i] - flag > mx) {
    			mx = max + deg[i] - flag;
    			y = i;
    		}
    	}
    }


    DSU d(n);
    for (auto & [u, v] : edges) {
    	if (u != x && u != y && v != x && v != y) {
    		d.merge(u, v);
    	}
    }

    int ans = 0;
    for (int i = 0; i < n; ++ i) {
    	if (i != x && i != y && d.find(i) == i) {
    		++ ans;
    	}
    }

    std::cout << ans << "\n";
}

D. Game With Triangles

题意:给你两个数组A,B,每次你可以从A选两个数和从B选一个数或者从B选两个和从A选一个,价值为选的两个数的差的绝对值。问最多操作多少次,每次的最大价值是多少。

显然我们应该学最大最小的,那么每次选A的最大最小或者B的最大最小,如果某一方没有了,那就把这个操作换成让另一个数组操作两遍就行了。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n), b(m);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < m; ++ i) {
    	std::cin >> b[i];
    }

    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());

    std::vector<int> A, B;
    for (int i = 0; i < n / 2; ++ i) {
    	A.push_back(a[n - 1 - i]- a[i]);
    }

    for (int i = 0; i < m / 2; ++ i) {
    	B.push_back(b[m - 1 - i] - b[i]);
    }

    std::vector<i64> ans;
    int x = 0, y = 0;
    i64 sum = 0;
    while (1) {
    	if (x >= A.size() && y >= B.size()) {
    		break;
    	}

    	if (n - (x * 2 + y) >= 2 && m - (x + y * 2) >= 2) {
    		if (A[x] >= B[y]) {
    			sum += A[x ++ ];
    		} else {
    			sum += B[y ++ ];
    		}
    	} else if (n - (x * 2 + y) >= 2 && m - (x + y * 2) >= 1) {
    		sum += A[x ++ ];
    	} else if (n - (x * 2 + y) >= 1 && m - (x + y * 2) >= 2) {
    		sum += B[y ++ ];
    	} else if (x * 2 + y == n && x - 1 + y * 2 + 4 <= m && y + 2 <= B.size() && x) {
    		sum -= A[ -- x];
    		A.pop_back();
    		sum += B[y ++ ];
    		sum += B[y ++ ];
    	} else if (x + y * 2 == m && x * 2 + y - 1 + 4 <= n && x + 2 <= A.size() && y) {
    		sum -= B[ -- y];
    		B.pop_back();
    		sum += A[x ++ ];
    		sum += A[x ++ ];
    	} else {
    		break;
    	}

    	ans.push_back(sum);
    }

    std::cout << ans.size() << "\n";
    for (int i = 0; i < ans.size(); ++ i) {
    	std::cout << ans[i] << " ";
    }
    std::cout << "\n";
}

E. Triangle Tree

题意:给你一棵树,对于任意两个点u,v,如果它们存在祖孙关系,则f(u,v)=0, 否则看有多少个数x可以和dist(u,lca(u,v)),dist(v,lca(u,v))组成三角形。设这样的xcntx个,则f(u,v)=cntx。求u=1nv=u+1nf(u,v)

观察三角形的性质,如果已经确定了两条边x,y,求第三条边z有多少个取值。这里假设x<y,则yx<z<y+x,因为是整数域,则可以表示为yx+1zy+x1,得到z的合法取值有2x1个。也就是说,f(u,v)=2×min(dist(u,lca(u,v)),dist(v,lca(u,v)))1
lca(u,v)=ld为点的深度,则f(u,v)=2×min(dudl,dvdl)1,等价于2×min(du,dv)2×dl1。那么我们可以将贡献分成三部分来算。不过考虑特殊处理u,v存在祖孙关系比较麻烦,将这种情况代入式子发现f(u,v)=1,则可以先不管这种情况,最后算完看有多少个点对会贡献-1,把这些值加起来就行。
现在观察这个式子f(u,v)=2×min(du,dv)2×dl1,在u=1nv=u+1nf(u,v)中,1总共算了n(n1)2次,可以直接计算。
再看2×dl,每个dl要算多少次要看它能被多少点对当作lca,那么可以dfs的时候计算所有子树能贡献多少点对,可以一棵一棵子树算,每棵子树和前面已经算过的子树的子树和做乘就行,然后累加子树和。
如何计算2×min(du,dv)?一种情况假定du是较小的值,那么它和所有深度大于它的节点都可以贡献一个du的值,可以把每个深度的个数存下来,dfs之后再单独计算。还有一种情况就是du==dv这种情况相当于从所有深度为du的节点中选两个出来,可以用组合数计算。
最后看存在祖孙关系的点对贡献了多少个-1,直接减去i=1n(sizei1)即可。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::vector<int> > adj(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    i64 ans = 0;
    std::vector<int> size(n), d(n + 1), cnt(n + 1), sum(n + 2);
    auto dfs = [&](auto self, int u, int fa) -> void {
    	cnt[d[u]] += 1;
    	size[u] = 1;
    	i64 tot = 0;
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		d[v] = d[u] + 1;
    		self(self, v, u);
    		tot += (i64)size[u] * size[v];
    		size[u] += size[v];
    	}

    	ans -= 2ll * d[u] * tot;
    	ans += size[u] - 1;
    };

    d[0] = 1;
    dfs(dfs, 0, -1);
    for (int i = n; i >= 1; -- i) {
    	sum[i] = cnt[i] + sum[i + 1];
    }

    for (int i = 1; i <= n; ++ i) {
    	ans += 2ll * i * ((i64)sum[i + 1] * cnt[i] + (i64)cnt[i] * (cnt[i] - 1) / 2);
    }

    ans = ans - (i64)n * (n - 1) / 2;

    std::cout << ans << "\n";
}
posted @   maburb  阅读(421)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示