VP Educational Codeforces Round 65 (Rated for Div. 2)


A. Telephone Number

题意:判断一个数字串删去若干字符到长度为11的时候能不能是8这个数字开头。

判断第一个8前面的数能不能都删去。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    int m = n - 11;
    int p = s.find('8');
    if (p == s.npos || p > m) {
    	std::cout << "NO\n";
    } else {
    	std::cout << "YES\n";
    }
}

B. Lost Numbers

题意:交互题。一个由\(\{{4, 8, 15, 16, 23, 42\}}\)组成的排列,一开始是隐藏的。你最多询问4次。每次问\(a_i \times a_j\)的值。求这个排列。

\(a_1\)\(a_2, a_3, a_4, a_5\)的乘积。就可以找出\(15\)或者\(23\)中至少一个数的位置。然后可以得到\(a_1\)的值,继而得到其它四个数的值。最后一个数就是没出现的那个数。

点击查看代码
int ask(int i, int j) {
	std::cout << "? " << i << " " << j << std::endl;
	int res;
	std::cin >> res;
	return res;
}

void solve() {
    std::set<int> s{4, 8, 15, 16, 23, 42};
    int ans[6]{}, b[6];
    for (int i = 1; i <= 4; ++ i) {
    	b[i] = ask(1, i + 1);
    }

    if (b[1] % 15 == 0 && b[2] % 15 == 0 && b[3] % 15 == 0 && b[4] % 15 == 0) {
    	ans[0] = 15;
    } else if (b[1] % 23 == 0 && b[2] % 23 == 0 && b[3] % 23 == 0 && b[4] % 23 == 0) {
    	ans[0] = 23;
    } else {
    	for (int i = 1; i <= 4; ++ i) {
    		if (b[i] % 15 == 0) {
    			ans[0] = b[i] / 15;
    			break;
    		} 

    		if (b[i] % 23 == 0) {
    			ans[0] = b[i] / 23;
    			break;
    		}
    	}
    }

    for (int i = 1; i <= 4; ++ i) {
    	ans[i] = b[i] / ans[0];
    }

    for (int i = 0; i <= 4; ++ i) {
    	s.erase(ans[i]);
    }

    ans[5] = *s.begin();
    std::cout << "! ";
    for (int i = 0; i <= 5; ++ i) {
    	std::cout << ans[i] << " ";
    }
    std::cout << std::endl;
}

C. News Distribution

题意:求一个点所在连通块的点的个数。

板子题。

点击查看代码
struct DSU {
	std::vector<int> fa, cnt;
	DSU(int _n) {
		init(_n);
	}

	void init(int _n) {
		fa.assign(_n, 0);
		cnt.assign(_n, 1);
		std::iota(fa.begin(), fa.end(), 0);
	}

	int find(int x) {
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}

	bool merge(int x, int y) {
		x = find(x), y = find(y);
		if (x == y) {
			return false;
		}

		fa[y] = x;
		cnt[x] += cnt[y];
		return true;
	}

	bool same(int x, int y) {
		return find(x) == find(y);
	}

	int size(int x) {
		return cnt[find(x)];
	}
};

void solve() {
    int n, m;
    std::cin >> n >> m;
    DSU d(n);
    for (int i = 0; i < m; ++ i) {
    	int k;
    	std::cin >> k;
    	std::vector<int> a(k);
    	for (int i = 0; i < k; ++ i) {
    		std::cin >> a[i];
    		-- a[i];
    	}

    	for (int i = 1; i < k; ++ i) {
    		d.merge(a[0], a[i]);
    	}
    }

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

D. Bicolored RBS

题意:把一个括号序列分成两份。使得其中嵌套括号最多的最小。

贪心的做。维护两个括号序列,每次遇到左括号就放到小的那个括号里,遇到右括号就把大的那个减小。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    std::string ans(n, '0');
    int cnt1 = 0, cnt2 = 0;
    for (int i = 0; i < n; ++ i) {
    	if (s[i] == '(') {
    		if (cnt1 <= cnt2) {
    			++ cnt1;
    		} else {
    			++ cnt2;
    			ans[i] = '1';
    		}
    	} else {
    		if (cnt1 >= cnt2) {
    			-- cnt1;
    		} else {
    			-- cnt2;
    			ans[i] = '1';
    		}
    	}
    }

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

E. Range Deleting

题意:一个数组\(a\),值域为\([1, x]\),求所有的\((l, r), 1 \leq l \leq r \leq x\)满足删去值在这个区间的数后数组非递减。

首先对于一个已经非递减的数组。不管这么删都是非递减的。那么这提示我们,如果\((l, r)\)合法,那么\((l, [r, x])\)都合法。
于是我们可以枚举\(l\),维护一个合法的\(r\)
我们只需要维护一个前缀是否合法,和一个后缀是否合法,然后看能不能把这个前缀和后缀拼起来也合法。可以记录\(L_i, R_i\)表示\(i\)出现的最左边和最右边的位置,那么\(l\)放到\(r\)的前面需要满足\(\max_{i=1}^{l} R_i \leq \min_{i=r}^{x} L_i\)

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

    std::vector<int> l(x + 2, n), r(x + 2, -1);
    for (int i = 0; i < n; ++ i) {
    	l[a[i]] = std::min(l[a[i]], i);
    	r[a[i]] = std::max(r[a[i]], i);
    }

    std::vector<int> st(x + 2), L(x + 2);
    st[x + 1] = 1; L[x + 1] = n;
    for (int i = x; i >= 1; -- i) {
    	if (r[i] < L[i + 1]) {
    		st[i] = 1;
    	} else {
    		break;
    	}

    	L[i] = std::min(L[i + 1], l[i]);
    }

    i64 ans = 0;
    for (int i = 1, maxr = -1, j = 1; i <= x; ++ i) {
    	j = std::max(i, j);
    	while (j <= x && (!st[j + 1] || maxr > L[j + 1])) {
    		++ j;
    	}

    	if (j > x) {
    		break;
    	}

    	ans += x - j + 1;
    	if (l[i] < maxr) {
    		break;
    	}

    	maxr = std::max(maxr, r[i]);
    }

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

F. Scalar Queries

题意:长度为\(n\)的数组\(a\)。定义\(f(l, r)\)价值为\(b = sort(a[l, .., r])\),也就是将这一段子数组排序后,价值为\(\sum_{i=1}^{r-l+1} b_i \times i\)。求\(\sum_{l=1}^{n} \sum_{r=l}^{n} f(l, r)\)

这种题,考虑单个点的贡献。也就是对于\(a_i\),它乘上的系数\(1, 2, ... n\)分别有多少个。发现并不好计算,考虑把大的贡献拆成小的贡献,设有\(k\)个区间使得\(a_i\)的排名小于等于\(j\),那么加上\(a_i \times k\)。也就是对于每个\(a_j < a_i, j < i\),我们加上\(j\times (n - i+ 1) \times a_i\)。发现对于\(a_i\)排名为\(1\)的区间,我们把其它排名大于等于\(1\)的区间也算上了,那么对于排名等于\(2\)的区间,本来要让\(a_i\)\(2\),但是我们在排名为\(1\)的区间多算了\(1\)的贡献,那么也只需要给每个区间算\(1\)的贡献。同理得对于排名为\(k\)的区间,因为\([1, k-1]\)都给这些区间算了一个贡献,那么这个区间只需要加上剩下\(1\)的贡献。
那么我们只需要把\(j < i\)\(j > i\)的情况分别算一遍。例如,对于\(j < i,a_j < a_i\),贡献为\(j\times (n - i+1)\times a_i\),发现\((n-i+1)\times a_i\)\(j\)无关,那么我们用树状数组记录前面每个\(a_j < a_i\)\(j\)的和。\(j > i\)的情况同理。
代码省略取模类。

点击查看代码
template <class T>
struct Fenwick {
    int n;
    std::vector<T> tr;

    Fenwick(int _n) {
        init(_n);
    }

    void init(int _n) {
        n = _n;
        tr.assign(_n + 1, T{});
    }

    void add(int x, const T &v) {
        for (int i = x; i <= n; i += i & -i) {
            tr[i] = tr[i] + v;
        }
    }

    T query(int x) {
        T res{};
        for (int i = x; i; i -= i & -i) {
            res = res + tr[i];
        }
        return res;
    }

    T sum(int l, int r) {
        return query(r) - query(l - 1);
    }
};

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    auto b = a;
    std::ranges::sort(b);
    b.erase(std::unique(b.begin(), b.end()), b.end());
    auto get = [&](int x) -> int {
    	return std::ranges::lower_bound(b, x) - b.begin() + 1;
    };

    int m = b.size();
    Fenwick<Z> tr(m + 1);
    Z ans = 0;
    for (int i = 0; i < n; ++ i) {
    	int k = get(a[i]);
    	tr.add(k, i + 1);
    	ans += tr.query(k) * (n - i) * a[i];
    }

    tr.init(m + 1);
    for (int i = n - 1; i >= 0; -- i) {
    	int k = get(a[i]);
    	ans += tr.query(k) * (i + 1) * a[i];
    	tr.add(k, n - i);
    }

    std::cout << ans << "\n";
}
posted @ 2025-04-22 22:12  maburb  阅读(8)  评论(0)    收藏  举报