VP Codeforces Round 906 (Div. 2)


A. Doremy's Paint 3

题意:给你你个数组,你要重排它使得每两个相邻数的和都相同。

\(a_1 + a_2 = a_2 + a_3\),那么\(a_1 = a_3\)\(a_2 + a_3 = a_3 + a_4\),那么\(a_2 = a_4\),然后发现奇数位置都相等,偶数位置都相等。那么数组只有一类数明显可以,如果有两类数,判断是不是有各占\(\frac{n}{2}\)个即可,注意\(n\)为奇数的情况有一类数会多占一个。

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

    int cnt = 0;
    for (int i = 0; i < n; ++ i) {
    	cnt += a[i] == a[0];
    }

    if ((s.size() == 2 && (cnt == n / 2 || cnt == (n + 1) / 2)) || s.size() == 1) {
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

B. Qingshan Loves Strings

题意:给你两个\(01\)\(s, t\),你每次可以把\(t\)插到\(s\)的任意位置,求能不能使得\(s\)相邻的数都不相同。

如果\(s\)本来不满足条件并且\(t\)也不满足条件,则无解。否则考虑插入\(t\),如果\(s_i == s_{i+1}\),那么则需要插入\(t\),判断\(t\)插入后是不是满足要求。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::string s, t;
    std::cin >> s >> t;

    bool flag = true;
    for (int i = 1; i < n; ++ i) {
    	flag &= s[i] != s[i - 1];
    }

    if (flag) {
    	std::cout << "YES\n";
    	return;
    }

    flag = true;
    for (int i = 1; i < m; ++ i) {
    	flag &= t[i] != t[i - 1];
    }

    if (flag) {
    	for (int i = 1; i < n; ++ i) {
    		if (s[i] == s[i - 1]) {
    			if (s[i - 1] == t[0] || s[i] == t.back()) {
    				std::cout << "NO\n";
    				return;
    			}
    		}
    	}

    	std::cout << "YES\n";
    	return;
    }

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

C. Qingshan Loves Strings 2

题意:给你一个\(01\)\(s\),你可以在任意位置插入\(01\),设最后\(s\)的长度为\(k\),求能不能使得最后所有的\(i \in [1, k], s_i != s_{n-i+1}\)

首先每次插入都会使得\(0\)\(1\)的数量加一,这不影响它们的差,那么一开始\(0\)\(1\)的个数就要相等。
假设\([1, i - 1], [n - i + 2, n]\)都已经满足要求,那么如果\(s_i == s_{n-i+1}\),当\(s_i == '0'\),则在\(s_{n-i+1}\)的后面插入\(01\) ,否则在\(i\)的前面插入\(01\),发现这样操作最后一定满足要求。暴力模拟即可。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
   	int cnt[2]{};
   	for (auto & c : s) {
   		++ cnt[c - '0'];
   	}

   	if (cnt[0] != cnt[1]) {
   		std::cout << -1 << "\n";
   		return;
   	}

   	std::vector<int> ans;

   	auto insert = [&](int p) -> void {
   		ans.push_back(p);
   		s = s.substr(0, p + 1) + "01" + s.substr(p + 1);
   	};

   	for (int l = 0, r = n - 1; l < r; ++ l, -- r) {
   		if (s[l] == s[r]) {
   			if (s[l] == '0') {
   				insert(r);
   				r += 2;
   			} else {
   				insert(l - 1);
   				r += 2;
   			}
   		}
   	}

   	std::cout << ans.size() << "\n";
   	for (auto & p : ans) {
   		std::cout << p + 1 << " \n"[p == ans.back()];
   	}
}

D. Doremy's Connecting Plan

题意:一开始有\(n\)个集合,第\(i\)个集合的值为\(a_i\)。你每次选择两个位置\(i, j\),设\(i\)所在集合的值为\(S_i\)\(j\)所在集合的值为\(S_j\)。那么如果\(S_i + S_j \geq i \times j \times c\)则可以合并这两个集合,新集合的值为\(S_a + S_b\)。求能不能合并成一个集合。

我们一定是选两个集合最小的两个位置操作,因为这样\(i \times j\)最小,那么发现,我们一开始一定可以选\(1\)和某个位置操作,利用反证法,假设一开始选择\((i, j)\)进行操作\(i \ne 1, j \ne 1\),并且\((1, i)\)\((1, j)\)都无法操作。那么有\(a_i + a_j \geq i \times j \times c\)\(a_i + a_1 < 1 \times i \times c\), \(a_j + a_1 < 1 \times j \times c\), 那么得到\(a_i + a_j + 2a_i < (i + j) \times c\),由于\(i \ne 1, j \ne 1\),那么\(i \times j \geq i + j\),于是产生矛盾。
那么我们现在改一下式子:\(S_i + S_j \geq i \times j \times c ==> S_i \geq i \times j \times c - S_j\),根据贪心策略,我们一定是每次选\(1\)和其它位置操作,那么式子就是\(S_1 \geq j \times c - S_j\),于是按\(j \times c - S_j\)排序,从小到大合并。

点击查看代码
void solve() {
    int n;
    i64 c;
    std::cin >> n >> c;
    std::vector<std::array<i64, 3>> a(n + 1);
    for (int i = 1; i <= n; ++ i) {
    	i64 x;
    	std::cin >> x;
    	a[i] = {i * c - x, x, i};
    }

    std::sort(a.begin() + 2, a.end());
    i64 sum = a[1][1];
    for (int i = 2; i <= n; ++ i) {
    	if (sum < a[i][0]) {
    		std::cout << "NO\n";
    		return;
    	}

    	sum += a[i][1];
    }

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

E1. Doremy's Drying Plan (Easy Version)

题意:有\(n\)个线段,你要删除其中两个,使得没有被线段覆盖过的点最多。

分成两种情况:

  1. 两个线段不相交
  2. 两个线段相交

我们先利用差分算出每个点被多少线段覆盖。
对于第一种情况,我们记录线段覆盖的区间中只被\(1\)个线段覆盖的点的最大值和次大值。如果最终答案为不相交的两个线段,那么显然求出来的就是答案,否则最终答案为两个相交的线段,则求出来的值一定小于第二种情况的值。
对于第二种情况,我们用优先队列存每个线段的右端点和左端点,先把线段存在数组里按左端点排序,然后从小到大枚举,对于每个左端点为\(i\)的线段都加进来,然后对于每个前面加进来的线段的右端点小于\(i\)的线段都删去,那么就得到了每个点被覆盖的线段。如果这个点恰好被两个线段覆盖,那么就取出来更新答案。

点击查看代码
void solve() {
    int n, m, k;
    std::cin >> n >> m >> k;
    using PII = std::pair<int, int>;
    std::vector<PII> segs;
    std::vector<int> d(n + 2);
    for (int i = 0; i < m; ++ i) {
    	int l, r;
    	std::cin >> l >> r;
        segs.push_back({l, r});
    	d[l] += 1;
    	d[r + 1] -= 1;
    }

    std::vector<int> one(n + 1), two(n + 1);
    int ans = 0;
    for (int i = 1; i <= n; ++ i) {
        d[i] += d[i - 1];
        one[i] = one[i - 1];
        two[i] = two[i - 1];
        if (d[i] == 0) {
            ++ ans; 
        } else if (d[i] == 1) {
            ++ one[i];
        } else if (d[i] == 2) {
            ++ two[i];
        }
    }

    int max1 = 0, max2 = 0;
    for (auto & [l, r] : segs) {
        int cnt = one[r] - one[l - 1];
        if (cnt >= max1) {
            max2 = max1;
            max1 = cnt;
        } else if (cnt > max2) {
            max2 = cnt;
        }
    }

    int ans1 = max1 + max2;
    std::sort(segs.begin(), segs.end());
    std::priority_queue<PII, std::vector<PII>, std::greater<>> heap;
    for (int i = 1, t = 0; i <= n; ++ i) {
        while (heap.size() && heap.top().first < i) {
            heap.pop();
        }

        while (t < m && segs[t].first == i) {
            heap.push({segs[t].second, segs[t].first});
            ++ t;
        }

        if (heap.size() == 2) {
            auto [a1, a2] = heap.top(); heap.pop();
            auto [a3, a4] = heap.top();
            std::vector<int> a{a1, a2, a3, a4};
            std::sort(a.begin(), a.end());
            int cnt = one[a[3]] - one[a[0] - 1] + two[a[2]] - two[a[1] - 1];
            ans1 = std::max(ans1, cnt);
            heap.push({a1, a2});
        }
    }

    std::cout << ans + ans1 << "\n";
}
posted @ 2025-02-24 21:17  maburb  阅读(21)  评论(0)    收藏  举报