Codeforces Round 1022 (Div. 2)


A. Permutation Warm-Up

题意:一个排列的值为\(\sum_{i=1}^{n} |p_i - i|\),求所有长度为\(n\)的排列有多少不同的值。

对于一个\(p_i = i\)的排列,我们可以通过不断交换两个位置的数把它变成任意的排列。发现每次交换两个数增加的值是偶数,那么值只会是偶数。
然后猜测所有偶数都可以拿到。那么求出最大的值看有多少偶数。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    int ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	ans += std::abs(i - (n - i + 1));
    }
    std::cout << ans / 2 + 1 << "\n";
}

B. SUMdamental Decomposition

题意:构造一个长度为\(n\)的正整数数组,要求异或和为\(x\)且总和最小。

分类讨论。
如果\(x=0\),那么\(n=1\)无解。如果\(n\)是偶数,那么构造\(n\)\(1\),否则构造\(1, 2, 3\)\(n-3\)\(1\)
否则我们记\(x\)二进制有\(cnt\)\(1\)。那么如果\(n \leq cnt\)我们可以把每一个\(1\)分别给一个位置满足每个位置都大于\(0\),答案就是\(x\)
否则我们应该尽量放\(1\),如果\(n - cnt\)是奇数,那么需要放\(n-cnt+1\)\(1\), 否则放\(n-cnt\)\(1\)。但如果\(x == 1\)\(n-cnt\)是奇数,那么我们需要放两个其它的数,再放\(n-cnt-1\)\(1\),这两个其它的数就选\(x\)二进制下没出现过的最低位就行。

点击查看代码
void solve() {
    i64 n, x;
    std::cin >> n >> x;
    if (x == 0) {
    	if (n == 1) {
    		std::cout << -1 << "\n";
    	} else if (n % 2 == 0) {
    		std::cout << n << "\n";
    	} else {
    		std::cout << 6 + (n - 3) << "\n";
    	}
    	return;
    }
    int cnt = __builtin_popcount(x);
    i64 min = 0;
    for (i64 i = 0; i < 30; ++ i) {
    	if (~x >> i & 1) {
    		min = 1 << i;
    		break;
    	}
    }

    if (n <= cnt) {
    	std::cout << x << "\n";
    } else {
    	i64 ans = x;
    	if (x & 1) {
    		if (n - cnt & 1) {
				if (cnt == 1) {
					ans += min * 2 + n - cnt - 1;
				} else {
					ans += n - cnt + 1;
				}
    		} else {
    			ans += n - cnt;
    		}
    	} else {
    		ans += n - cnt + (n - cnt) % 2;
    	}
    	std::cout << ans << "\n";
    }
}

C. Neo's Escape

题意:\(n\)个数,需要从大到小选择,如果新选择的数旁边有已经选择过的数,则可以合并,否则答案加一。

看懂题模拟即可。
考虑按值排序,然后从大到小找连续相同的段,这些段只需要选一个元素就能合并这个段的所有元素。如果这段两边有已经选过的数,则不需要再选。每次把当前段标记选过就行。

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

   	std::ranges::sort(a.begin() + 1, a.end(), std::greater<>());
    int ans = 0;
    std::vector<int> st(n + 2);
    for (int i = 1; i <= n; ++ i) {
    	int j = i;
    	while (j + 1 <= n && a[i].first == a[j + 1].first && a[j].second - 1 == a[j + 1].second) {
    		++ j;
    	}
    	ans += st[a[j].second - 1] == 0 && st[a[i].second + 1] == 0;
    	for (int k = i; k <= j; ++ k) {
    		st[a[k].second] = 1;
    	}	
    	i = j;	
    }
    std::cout << ans << "\n";
}

D. Needle in a Numstack

题意:交互题。有两个长度大于等于\(k\)的隐藏数组\(a, b\),它们总长度为\(n\),只包含\([1, k]\)之间的数字。\(a, b\)中任选\(k\)个连续的元素都不一样。\(a, b\)相接成为了\(c\),每次可以询问\(c_i\)的值。最多问\(250\)次,\(k \leq 50, n \leq 10^6\)\(n\)在所有测试中无限制。求\(a, b\)的长度。

任选\(k\)个连续元素都不同,且值域为\([1, k]\),意味着\(a, b\)每一个长度为\(k\)段都是一个排列,那么只需要求出\(a\)的前\(k\)个就能推出\(a_{i+k} = a_i\)。也就得到了\(a\)数组。同理询问\(k\)次得到\(b\)数组。这时我们只需要比较\(a, b\)不同的位置就行了,因为分界点一定就在这些位置上。
如果\(n\)在所有样例和有限制,那么我们可以直接把所有不同的位置存下来,然后发现这些位置一定是左边一部分属于\(a\),右边一部分属于\(b\),那么可以二分求出来。但现在\(n\)无限制,每次不同的位置可能有\(n-2*k\)个,也就是遍历就是\(O(n)\)的时间,\(t\)组测试直接超时。
我们可以只保留\(a, b\)的前\(k\)个数,因为它们是关于着前\(k\)个数的循环,那么对于一个位置\(a_i \ne b_i\)意味着有\(\lfloor \frac{n}{k} \rfloor\)个这样的位置。我们把这些位置取出来记为\(p_1, p_2, p_3...p_m\)。那么我们可以先查询最右边属于\(a\)\(p_1\),也就是\(cnt \times k + p_1\)属于\(a\)的最大\(cnt\),这个可以二分查询。那么就得到了\((cnt + 1) \times k + p_1\)一定不属于\(a\),则答案就在\(cnt \times k + p_1, cnt \times k + p_2 .. cnt \times k + p_m\)里。则可以再次二分求得答案。
注意一些细节,我们求得的是属于\(a\)的和\(b\)不同的位置中最右边的,但这些位置中间有一些\(a, b\)相同的位置,那么我们求得的这个位置他必须和下一个不同的位置挨着,不然答案就是\(-1\)

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

void solve() {
    int n, k;
    std::cin >> n >> k;
    if (n == 2 * k) {
    	std::cout << "! " << k << " " << k << std::endl;
    	return;
    }

    std::vector<int> a(k), b(k);
    for (int i = 0; i < k; ++ i) {
        a[i] = ask(i);
    }

    for (int i = n - k; i < n; ++ i) {
        b[i % k] = ask(i);
    }

    std::vector<int> p;
    for (int i = 0; i < k; ++ i) {
        if (a[i] != b[i]) {
            p.push_back(i);
        }
    }

    if (p.empty()) {
        std::cout << "! " << -1 << std::endl;
        return;
    }

    int l = 0, r = (n - k - p[0]) / k;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (ask(p[0] + k * mid) == a[p[0]]) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }

    int pos = k * l;
    l = 0, r = (int)p.size() - 1;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (ask(pos + p[mid]) == a[p[mid]]) {
            l = mid;    
        } else {
            r = mid - 1;
        }
    }


    int p1 = std::max(pos + p[l] + 1, k);
    int p2 = std::min(pos + (l == (int)p.size() - 1 ? p[l] + k - p[l] + p[0] : p[l + 1]) + 1, n - k + 1);
    if (p1 + 1 != p2) {
        std::cout << "! " << -1 << std::endl;
        return;
    }
    std::cout << "! " << p1 << " " << n - p1 << std::endl;
}
posted @ 2025-05-02 01:58  maburb  阅读(230)  评论(2)    收藏  举报