【Codeforces思维题】20220728

2022.07.28

1582D. Vupsen, Pupsen and 0

题目:

题目链接

题解:
考虑确定 b i b_i bi b j b_j bj 使得 a i × b i + a j × b j = 0 a_i\times b_i+a_j\times b_j=0 ai×bi+aj×bj=0,当 b i = a j b_i=a_j bi=aj b j = − a i b_j=-a_i bj=ai 是一种合法的方案。

n n n 为偶数时, ∑ i = 1 n ∣ b i ∣ = ∑ i = 1 n ∣ a i ∣ ≤ 1 0 9 \sum\limits_{i=1}^n |b_i|=\sum\limits_{i=1}^n |a_i|\leq 10^9 i=1nbi=i=1nai109是显然的。

n n n 为奇数时,两两匹配会多出一个数,这个数与任意一组构成一个三数的组。
设这三个数为 a i , a j , a k a_i,a_j,a_k ai,aj,ak ,可以证明的是三个数中任选两个数,至少有一种情况下的两个数之和不为 0 0 0(证明如下)。如此就满足了 b b b 中任意数都不为 0 0 0 的要求了
假设 a i + a j ≠ 0 a_i+a_j\neq 0 ai+aj=0,那么 b i = b j = a k , b k = − ( a i + a j ) b_i=b_j=a_k, b_k=-(a_i+a_j) bi=bj=ak,bk=(ai+aj)
因为 n < 1 0 5 n<10^5 n<105,所以在极限最坏情况下, ∑ i = 1 n ∣ b i ∣ = ∑ i = 1 n ∣ a i ∣ + max ⁡ { a i } = ∑ i = 1 n ∣ a i ∣ + 1 0 4 = 1 0 9 \sum\limits_{i=1}^n |b_i|=\sum\limits_{i=1}^n |a_i|+\max\{a_i\}=\sum\limits_{i=1}^n |a_i|+10^4=10^9 i=1nbi=i=1nai+max{ai}=i=1nai+104=109

本题值域为 [ − 1 0 4 , − 1 ] [-10^4,-1] [104,1] [ 1 , 1 0 4 ] [1,10^4] [1,104],而数量为 1 0 5 10^5 105,在这种情况下必然是存在绝对值相同的元素,可以降低 b i b_i bi 的绝对值,但是在此不做细致的讨论了。

关于证明:
题目中已说明 a a a 中任意的数都不等于 0 0 0,下面采用反证法证明
a i + a j = 0 a_i+a_j=0 ai+aj=0
a j + a k = 0 a_j+a_k=0 aj+ak=0
a i + a k = 0 a_i+a_k=0 ai+ak=0
推导出: a i = a j = a k = 0 a_i=a_j=a_k=0 ai=aj=ak=0,与题意不符,所以不存在三个数中任选两个数的和都为 0 0 0

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 100010;
int a[N], n;

void solve() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	int fir = 1;
	if (n & 1) {
		if (a[1] + a[2] != 0) {
			int v1 = a[1] + a[2];
			int v2 = a[3];
			a[1] = a[2] = v2, a[3] = -v1;
		} else if (a[1] + a[3] != 0) {
			int v1 = a[1] + a[3];
			int v2 = a[2];
			a[1] = a[3] = v2, a[2] = -v1;
		} else if (a[2] + a[3] != 0) {
			int v1 = a[2] + a[3];
			int v2 = a[1];
			a[2] = a[3] = v2, a[1] = -v1;
		}
		
		fir += 3;
	}
	
	for (int k = fir; k + 1 <= n; k += 2) {
		int v1 = a[k], v2 = a[k + 1];
		if (v1 * v2 > 0) a[k] = v2, a[k + 1] = -v1;
		else a[k] = -v2, a[k + 1] = v1;
	}
	
	for (int i = 1; i <= n; ++i) printf("%d%c", a[i], " \n"[i == n]);
}

int main()
{
	int T = 1;
	scanf("%d", &T);
	for (int i = 1; i <= T; ++i) {
		solve();
	}
	return 0;
}

1583D. Omkar and the Meaning of Life

题目:

题目链接

题解:
由于返回的是第一个和出现两次的最早索引,可以考虑小于 p n p_n pn 的数的个数来确定 p n p_n pn 的大小,并且通过其他数与 p n p_n pn 的大小关系来确定对应的值。

首先设定 a n = 1 a_n=1 an=1,当 a 1 a_1 a1 a n − 1 a_{n-1} an1 都相同, s 1 s_1 s1 s n − 1 s_{n-1} sn1 必然不同,因此返回的 k k k 必然是存在 s k = s n s_k=s_n sk=sn的。将 a 1 a_1 a1 a n − 1 a_{n-1} an1 依次设定为 2 − n 2-n 2n,找到比 p n p_n pn 小的数,并且设定这些数与 p n p_n pn 的差,统计小于 p n p_n pn 数的个数 c n t cnt cnt,那么 p n p_n pn 就是 c n t + 1 cnt+1 cnt+1

其次设定 a 2 a_2 a2 a n a_n an 1 1 1 a 1 a_1 a1 依次设定为 2 − n 2-n 2n,找到比 p n p_n pn 大的数,并且设定这些数与 p n p_n pn 的差。

最后通过这些差确定所有的 p i p_i pi

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 110;
int a[N], n;
int ch[N]; 
int res;

int ask(int a, int b) {
	printf("?");
	for (int i = 1; i < n; ++i) printf(" %d", a);
	printf(" %d", b);
	puts("");
	fflush(stdout);
	
	scanf("%d", &res);
	return res;
}

void solve() {
	scanf("%d", &n);
	a[n] = 1;
	for (int i = 2; i <= n; ++i) {
		int k = ask(i, 1);
		if (!k) break;
		a[n] += 1;
		ch[k] = 1 - i;
	}
	
	for (int i = 2; i <= n; ++i) {
		int k = ask(1, i);
		if (!k) break;
		ch[k] = i - 1;
	}
	
	printf("!");
	for (int i = 1; i <= n; ++i) printf(" %d", a[n] + ch[i]);
	puts("");
	fflush(stdout);
}

int main()
{
	int T = 1;
//	scanf("%d", &T);
	for (int i = 1; i <= T; ++i) {
		solve();
	}
	return 0;
}

1592C. Bakry and Partitioning

题目:

题目链接

题解:
首先求出所有点的异或和

  • 当异或和为 0 0 0,断开任意一个叶子即可
  • 当异或和为 s s s,那么必然是分成奇数个异或和为 s s s 的连通分量
    这里在 d f s dfs dfs 时,找到一个连通分量的异或和为 s s s 时,统计连通分量异或和为 s s s 的个数,并清空临时异或值。
    • k = 2 k=2 k=2 时,只能拆成两个连通分量,必然不存在合法方案
    • k > 2 k>2 k>2 时,当异或和为 s s s 的连通分量为 c n t ( c n t ≥ 3 ) cnt(cnt\geq 3) cnt(cnt3) 个,这个 c n t cnt cnt 必然是个奇数,就可以拆成两个由 1 1 1个异或和为 s s s 的连通分量和一个由 c n t − 2 cnt-2 cnt2 个异或和为 s s s 的连通分量构成。如下图所示,每个点代表一个连通分量。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 100010, M = N << 1;
int h[N], e[M], ne[M], idx;
int a[N], n, k;
int sum, cnt;
bool ok;

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u, int fa) {
	int s = a[u];
	for (int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if (v != fa) {
			s ^= dfs(v, u);
		}
	}
	
	if (s == sum) cnt += 1, s = 0;
	
	return s;
}

void solve() {
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), h[i] = -1;
	idx = 0; cnt = 0;
	for (int i = 1, a, b; i < n; ++i) {
		scanf("%d%d", &a, &b);
		add(a, b);
		add(b, a);
	}
	
	sum = 0;
	for (int i = 1; i <= n; ++i) sum ^= a[i];
	if (sum == 0) {
		puts("YES"); 
	} else {
		if (k == 2) {
			puts("NO");
			return ;
		}

		dfs(1, -1);
		if (cnt >= 3) puts("YES");
		else puts("NO");
	}
}

int main()
{
	int T = 1;
	scanf("%d", &T);
	for (int i = 1; i <= T; ++i) {
		solve();
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值