传智杯-省赛-第二场(B组)题解
小苯点兵点将
思路:签到题,从到
遍历判断可以;也可以求出 [1,l-1] 中 3 倍数的个数,和 [1,𝑟]中 3 倍数的个数,作差判断结果是否大于等于 1
void solve()
{
int l, r;
cin >> l >> r;
if (r/3-(l-1)/3 >= 1) cout << "YES" << endl;
else cout << "NO" << endl;
}
void solve()
{
int l, r;
cin >> l >> r;
for (int i = l; i <= r; i++){
if (i% 3 == 0){
cout << "YES" << endl;
return;
}
}
cout << "NO" << endl;
}
小苯的好数
思路:区间查询很容易想到前缀和来做,因此就是考怎么来判断是否是好数,这里为偶数时, 肯定是好数,主要就是判断奇数怎么是好数,这里正解是要证明的(但我太菜了,只能打表来观察了),得出哪些奇数是好数,哪些不是:接下来是打表函数:
void solve()
{
for (int i = 1; i<=99; i++){
if (i%2 == 1){
int sum = 1;
for (int j = 2; j<i; j++){
if (i%j == 0) sum += j;
}
if (sum % 2 == 0) cout << i << endl;
}
}
}
观察出只有平方数的因子和才能是偶数,所以就可以使用前缀和来解决此题
void solve() {
int n, q;
cin >> n >> q;
vector<int> s(n + 1);
auto check = [&](int x) -> bool {
if(x % 2 == 0) return 1;
int sq = sqrt(x);
return sq * sq == x;
};
for(int i = 1, x; i <= n; i++) {
cin >> x;
s[i] = s[i - 1] + check(x);
}
while(q -- ) {
int l, r;
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
}
小苯的ovo
思路:动态规划,有点像背包问题,这里要找出cost为修改次数,并通过动态转移。注意dp数组的初始化
void solve()
{
int n, m, k;
cin >> n >> k;
vector<vector<int>> dp(n+1, vector<int>(k+1, -INF));
// dp[i][k]表示前i个字符,最多修改k次的最大个数
string s;
cin >> s;
s = " "+s;
if (n < 3){
cout << 0 << endl;
return;
}
dp[0][0] = 0, dp[1][0] = 0, dp[2][0] = 0;
for (int i = 3; i<=n; i++){
int num = (s[i-2]!='o')+(s[i-1]!='v')+(s[i]!='o');
dp[i] = dp[i-1];
for (int j = num; j<=k; j++){
dp[i][j] = max(dp[i][j], dp[i-3][j-num]+1);
}
}
cout << *max_element(dp[n].begin(), dp[n].end()) << endl;
}
小苯的水瓶
思路:看到取最值应该使用二分,这里注意check函数的编写,并主要need大于k+m时提前结束,或者开_i128,防止爆longlong。
void solve()
{
int n, m, k;
cin >> n >> m >> k;
vi a(n);
for (int i = 0; i<n; i++) cin >> a[i];
sort(a.begin(), a.end());
auto check = [&](int x)->bool
{
int xu=0, need = 0;
for (int i = 0; i<n; i++){
xu += max(0ll, a[i]-x);
need += max(0ll, x-a[i]);
if (need > k+m) return false;
}
return k+min(xu, m)-need >= 0;
};
int l = 0, r = 1e9;
while (l+1 < r){
int mid = (l+r)/2;
if (check(mid)) l = mid;
else r = mid;
}
cout << l << endl;
}
小苯的旅行计划
思路:因为m的值不是很大,所以我们可以来枚举要使用魔法的边来找最小值,需要记录每条边有多少个人的记录,这里应该用树上边差分来统计, 再在枚举时取最大值,思路还是比较简单,但代码实现上还是有点难度。
struct LCA{
vector<vector<int>> f;
vector<int> sum, dep;
vector<vector<pair<int, int>>> g;
LCA(int n){
f.resize(20, vector<int>(n+1));
dep.resize(n+1), sum.resize(n+1);
g.resize(n+1);
}
void dfs(int u, int fa){
dep[u] = dep[fa] + 1;
f[0][u] = fa;
for (int i = 1; i<20; i++){
f[i][u] = f[i-1][f[i-1][u]];
}
for (auto [v, w] : g[u]){
if (v == fa) continue;
sum[v] = sum[u]+w;
dfs(v, u);
}
}
int find(int u, int v){
if (dep[u] < dep[v]) swap(u, v);
for (int i = 19; i>=0; i--){
if (dep[f[i][u]] >= dep[v]) u = f[i][u];
}
if (u == v) return u;
for (int i = 19; i>=0; i--){
if (f[i][u] != f[i][v]){
u = f[i][u];
v = f[i][v];
}
}
return f[0][u];
}
};
struct node{
int u, v, w;
};
void solve(){
int n, m;
cin >> n >> m;
LCA lca(n);
vector<node> edge(n+1);
for (int i=1,u,v,w; i<=n-1; i++){
cin >> u >> v >> w;
lca.g[u].push_back(make_pair(v, w));
lca.g[v].push_back(make_pair(u, w));
edge.push_back({u, v, w});
}
lca.dfs(1, 0);
int cost = 0;
vector<int> s(n+1);
for (int i = 0,a,b; i<m; i++){
cin >> a >> b;
int ll = lca.find(a, b);
s[a]++, s[b]++, s[ll]-=2;
cost += (lca.sum[a]+lca.sum[b]-2*lca.sum[ll]);
}
auto dfs2 = [&](auto &&dfs2, int u, int fa)->void
{
for (auto [v, w]: lca.g[u]){
if (v == fa) continue;
dfs2(dfs2, v, u);
s[u] += s[v];
}
};
dfs2(dfs2, 1, 0);
int mx = 0;
for (auto [u, v, w]:edge){
if (lca.dep[u] < lca.dep[v]) swap(u, v);
mx = max(mx, s[u]*w);
}
cout << cost - mx << endl;
}
struct Edge{
int u, v, w;
};
void solve()
{
int n, m, k;
cin >> n >> m;
vector<vector<pair<int,int>>> g(n+1);//记录连接边
vector<Edge> edge; //记录m条边
for (int i = 1; i<=n-1; i++){
int u, v, w;
cin >> u >> v >> w;
g[u].pb(mp(v,w));
g[v].pb(mp(u,w));
edge.pb({u,v,w});
}
vector<vector<int>> f(20, vector<int>(n+1)); //父节点
vector<int> dep(n+1), sum(n+1); //深度 树的前缀和(从根到叶递增)
auto dfs1= [&](auto && dfs1, int u, int fa)->void //倍增预处理
{
dep[u] = dep[fa]+1;
f[0][u] = fa;
for (int j = 1; j<20; j++){
f[j][u] = f[j-1][f[j-1][u]];
}
for (auto & [v,w] : g[u]){
if (v == fa) continue;
sum[v] = sum[u] + w;
dfs1(dfs1, v, u);
}
};
dfs1(dfs1, 1, 0);
auto LCA = [&](int u, int v)->int //求最近公共祖先
{
if (dep[u] < dep[v]) swap(u,v);
for (int j = 19; j>=0; j--){
if (dep[f[j][u]] >= dep[v]) u = f[j][u];
}
if (u == v) return u;
for (int j = 19; j>=0; j--){
if (f[j][u] != f[j][v]){
u = f[j][u];
v = f[j][v];
}
}
return f[0][u];
};
int cost = 0;
vector<int> s(n+1);
for (int i = 0, a, b; i<m; i++){
cin >> a >> b;
int lca = LCA(a,b);
s[a]++, s[b]++, s[lca]-=2;
cost += (sum[a]+sum[b]-2*sum[lca]); // 算出原本的边权前缀和(不使用魔法时,全部的需要的花费)
}
auto dfs2 = [&](auto && dfs2, int u, int fa)->void //利用差分求每条边经过的次数
{
for (auto [v,w]:g[u]){
if (v == fa) continue;
dfs2(dfs2, v, u);
s[u]+=s[v];
}
};
dfs2(dfs2, 1, 0);
int mx = 0;
for (auto [u,v,w] : edge){
if (dep[u] < dep[v]) swap(u,v); // 这里是因为边权是下降到点的,所以要用深度更深的那个点
mx = max(mx, s[u]*w);
}
cout << cost-mx << endl;
}
小苯的奇怪最短路
思路:
考虑枚举答案中的最大边 w,那么问题变成了最小边如何求。
让我们考虑最小生成树的求法,在合并连通块的过程中,如果 (u,v)连通则我们直接跳过,否则连上边,并给总权加上 (u,v)的权。
在本题中实际上也类似,我们给每个连通块都维护一个当前连通块中的最小边 mn,接着正常执行
𝑘𝑟𝑢𝑠𝑘𝑎𝑙求最小生成树,如果当前 1 和 n 已经连通,则我们就对 1,n所在的连通块的最小边 mn 加上当前边 w 取 min即可,在过程中要一直维护每个连通块中最小边的权 mn
struct edge{
int u, v, w;
bool operator < (const edge&e) const{
return w < e.w;
}
};
struct ufSet{
vector<int> fa,siz;
// fa[N]用于存储每个元素的父节点信息
// siz[N]用于存储每个集合的大小
vector<int> mn;
void init(int n){
fa.resize(n+5),siz.resize(n+5);
mn.resize(n+5);
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1,mn[i]=INF;
}
int find(int x){
if(x==fa[x]) return x;
fa[x]=find(fa[x]);
return fa[x];
}
bool mrg(int x,int y, int w){
x=find(x),y=find(y);
// if(x==y) return 0;
if(siz[x]<siz[y]) swap(x,y);
fa[y]=x,siz[x]+=siz[y];
mn[x]=min({mn[x],w, mn[y]});
return 1;
}
}t;
void solve()
{
int n, m, k;
cin >> n >> m;
t.init(n);
vector<edge> e;
for (int i = 0, u, v, w; i<m; i++){
cin >> u >> v >> w;
e.pb({u, v, w});
}
sort(e.begin(), e.end());
int ans = INF;
for (auto [u, v, w]: e){
t.mrg(u, v, w);
if (t.find(1) == t.find(n)){
ans = min(ans, t.mn[t.find(1)]+w);
}
}
if (ans > 1e17) ans = -1;
cout << ans << endl;
}