CF系列题解(本场实况异常精彩,可惜题主要早八,结束就睡了,t宝有点可惜,jls我滴超人!!!)
A. Good Pairs
给定一个长度为 n n n 的数组 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an ,一个数对 ( i , j ) (i,j) (i,j) 被称为 好 时需要满足:对于所有的 1 ≤ k ≤ n 1≤k≤n 1≤k≤n , ∣ a i − a k ∣ + ∣ a k − a j ∣ = ∣ a i − a j ∣ |a_i-a_k|+|a_k-a_j|=|a_i-a_j| ∣ai−ak∣+∣ak−aj∣=∣ai−aj∣ 。找到一个 好 的数对,注意: i i i 可以等于 j j j 。
输入格式
第一行包含一个整数 t ( 1 ≤ t ≤ 3000 ) t (1≤t≤3000) t(1≤t≤3000) ,表示有t组测试数据。
每个测试数据第一行包含一个个整数 n ( 1 ≤ n ≤ 1 0 5 ) n (1≤n≤10^5) n(1≤n≤105) 。
每个测试数据第二行包含 n n n 个整数 a 1 , a 2 , … , a n ( 1 ≤ a i ≤ 1 0 9 ) a_1,a_2,…,a_n (1≤a_i≤10^9) a1,a2,…,an(1≤ai≤109) 表示给定的序列。
数组总长度不超过 2 ⋅ 1 0 5 2⋅10^5 2⋅105 。
输出格式
输出任意一个 好 的数对。
输入样例:
3
3
5 2 7
5
1 4 2 2 3
1
2
输出样例:
2 3
1 2
1 1
由于所有的数均为正数,我们不妨假设 a i ≤ a k ≤ a j a_i≤a_k≤a_j ai≤ak≤aj ,当 a i a_i ai 为序列最小值, a j a_j aj 为序列最大值时,把 ∣ a i − a k ∣ + ∣ a k − a j ∣ = ∣ a i − a j ∣ |a_i-a_k|+|a_k-a_j|=|a_i-a_j| ∣ai−ak∣+∣ak−aj∣=∣ai−aj∣ 去掉绝对值得 a k − a i + a j − a k = a j − a i a_k-a_i+a_j-a_k=a_j-a_i ak−ai+aj−ak=aj−ai ,左右两边恒成立。
由于可能存在多种情况,其他得构造方法也是可以的。
#include
// #define int long long
using namespace std;
typedef long long LL;
void solve()
{
int n;
cin >> n;
vector<pair<int, int>> a(n);
for (int i = 0; i < n; i ++ ) {
int x;
cin >> x;
a[i] = {x, i};
}
sort(a.begin(), a.end());
cout << a[0].second + 1 << " " << a[n - 1].second + 1 << "\n";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T -- ) {
solve();
}
return 0;
}
B. Subtract Operation
给定序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an ,每次操作可以选择一个数 a t a_t at ,其他得数减去 a t a_t at ,问执行 n − 1 n - 1 n−1 次操作后能否得到给定数字 k k k 。
输入格式
第一行包含一个整数 t ( 1 ≤ t ≤ 1 0 4 ) t (1≤t≤10^4) t(1≤t≤104) ,表示有t组测试数据。
每个测试数据第一行包含两个整数 n , k ( 2 ≤ n ≤ 2 ⋅ 1 0 5 , 1 ≤ k ≤ 1 0 9 ) n, k (2≤n≤2⋅10^5, 1≤k≤10^9) n,k(2≤n≤2⋅105,1≤k≤109)。
每个测试数据第二行包含 n n n 个整数 a 1 , a 2 , … , a n ( − 1 0 9 ≤ a i ≤ 1 0 9 ) a_1,a_2,…,a_n(−10^9≤a_i≤10^9) a1,a2,…,an(−109≤ai≤109) 表示给定的序列。
n n n 的总和不会超过 2 ⋅ 1 0 5 2⋅10^5 2⋅105 。
输出格式
若能得到 k k k ,输出 YES
,否则输出 NO
。
输入样例:
4
4 5
4 2 2 7
5 4
1 9 1 3 4
2 17
17 0
2 17
18 18
输出样例:
YES
NO
YES
NO
直接讨论任意三个数 a i , a j , a k a_i,a_j,a_k ai,aj,ak 的情况。假设我们先删除 a i a_i ai ,序列变为 a j − a i , a k − a i a_j - a_i, a_k - a_i aj−ai,ak−ai ,此时我们再删除 a j − a i a_j - a_i aj−ai ,序列变为 a k − a j a_k - a_j ak−aj ,我们发现最终剩余的数与你删除的过程无关,只与最后剩余的两个数的差有关,因此题目就变为了查找序列中是否存在一对 a i , a j a_i,a_j ai,aj ,使得它们的差为 k k k 。
直接枚举 O ( n 2 ) O(n^2) O(n2) 显然不行,用 set
随便搞一搞即可。
#include
#define int long long
using namespace std;
typedef long long LL;
void solve()
{
int n, k;
cin >> n >> k;
set<int> S;
for (int i = 0; i < n; i ++ ) {
int x;
cin >> x;
S.insert(x);
}
for (auto x : S) {
if (S.count(x + k)) {
cout << "YES\n";
return;
}
}
cout << "NO\n";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T -- ) {
solve();
}
return 0;
}
C. Make Equal With Mod
给定一个长度为 n n n 的数组,你可以选择一个数 k k k ,将数组中每个 a i a_i ai 变为 a i m o d k a_i mod k aimodk ,可以进行若干次操作,问最终的序列是否相等。
输入格式
第一行包含一个整数 t ( 1 ≤ t ≤ 1 0 4 ) t (1≤t≤10^4) t(1≤t≤104) ,表示有t组测试数据。
每个测试数据第一行包含一个整数 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1≤n≤2⋅10^5) n(1≤n≤2⋅105) ,表示序列长度。
每个测试数据第二行包含 n n n 个整数 a 1 , a 2 , … , a n ( − 1 0 9 ≤ a i ≤ 1 0 9 ) a_1,a_2,…,a_n(−10^9≤a_i≤10^9) a1,a2,…,an(−109≤ai≤109) 表示给定的序列。
n n n 的总和不会超过 2 ⋅ 1 0 5 2⋅10^5 2⋅105 。
输出格式
若经过若干次操作序列相等,输出 YES
,否则输出 NO
。
输入样例:
4
4
2 5 6 8
3
1 1 1
5
4 1 7 0 8
4
5 9 17 5
输出样例:
YES
YES
NO
YES
分类讨论:
代码写的有点丑。。。
#include
#define int long long
using namespace std;
typedef long long LL;
void solve()
{
int n;
cin >> n;
set<int> S;
int cnt0 = 0, cnt1 = 0; // 统计0和1的数量
for (int i = 0; i < n; i ++ ) {
int x;
cin >> x;
S.insert(x);
if (x == 1) cnt1 ++ ;
if (x == 0) cnt0 ++ ;
}
if (cnt1 == 0) { // 第一种情况
cout << "YES\n";
} else if (cnt1 && cnt0) { // 第二种情况
cout << "NO\n";
} else { // 第三种情况
for (auto x : S) {
if (S.count(x + 1)) { // 判断是否有连续的两个数,这里采用了set判断
cout << "NO\n";
return; // 及时return
}
}
cout << "YES\n";
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T -- ) {
solve();
}
return 0;
}
D. K-good
给定一个整数 n n n ,问 n n n 能否分成 k ( k ≥ 2 ) k(k≥2) k(k≥2) 个数的和并且这 k k k 个数模 k k k 的余数两两不同。
输入格式
第一行包含一个整数 t ( 1 ≤ t ≤ 1 0 5 ) t (1≤t≤10^5) t(1≤t≤105) ,表示有t组测试数据。
每组测试数据包含一个整数 n ( 2 ≤ n ≤ 1 0 18 ) n (2≤n≤10^{18}) n(2≤n≤1018) 。
输出格式
输出 k k k ,若无法分解,输出 − 1 -1 −1 。
输入样例:
5
2
4
6
15
20
输出样例:
-1
-1
3
3
5
根据题意我们可得: n − k ( k − 1 ) / 2 ≡ 0 n - k(k - 1) / 2 \equiv 0 n−k(k−1)/2≡0 ( m o d mod mod k k k) 。
不妨将 n n n 分解为 2 x × t 2^x ×t 2x×t , t t t 为奇数。
奇数因子为 t t t ,即 k = t k=t k=t。
偶数因子为 2 x 2^x 2x ,即 k / 2 = 2 x k/2=2^x k/2=2x 。
同时要满足 n − k ( k − 1 ) / 2 ≥ 0 n - k(k - 1) / 2 ≥0 n−k(k−1)/2≥0 ,因此 k k k 我们可以取的尽可能小,而且由于 k ≥ 2 k≥2 k≥2 ,所以当 t = 1 t=1 t=1 时 k k k 只能取 2 x 2^x 2x 且此时的 2 x = n 2^x=n 2x=n ,不满足我们的不等式,因此无解。
剩下的我们由于需要满足上述不等式,因此取 m i n ( t , 2 x + 1 ) min(t, 2^{x+1}) min(t,2x+1) 即可(严格证明回头补一下,目前还没有太清楚,感觉可以就写了哈哈哈)。
#include
#define int long long
using namespace std;
typedef long long LL;
void solve()
{
int n;
cin >> n;
int k = 1;
while (n % 2 == 0) {
n /= 2;
k *= 2;
}
if (n == 1) {
cout << "-1\n";
} else {
cout << min(n, k * 2) << "\n";
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T -- ) {
solve();
}
return 0;
}
E. Equal Tree Sums
给出一棵树,请为每个结点赋点权,使得对于任意的 i i i ,删去点 i i i 后,树分为若干连通块,每个连通块的点权和相等。
输入格式
第一行包含一个整数 t ( 1 ≤ t ≤ 1 0 4 ) t (1≤t≤10^4) t(1≤t≤104) ,表示有t组测试数据。
每个测试数据第一行包含一个个整数 n ( 3 ≤ n ≤ 1 0 5 ) n (3≤n≤10^5) n(3≤n≤105),表示节点个数。
接下来 n − 1 n-1 n−1 行每行包含 2 2 2 个整数 u , v ( 1 ≤ u , v ≤ n ) u,v (1≤u,v≤n) u,v(1≤u,v≤n) 表示一条边。
n n n 的总和不超过 1 0 5 10^5 105 。
输出格式
每个测试用例输出 n n n 个整数 a 1 , a 2 , … , a n ( − 1 0 5 ≤ a i ≤ 1 0 5 , a i ≠ 0 ) a_1,a_2,…,a_n(−10^5≤a_i≤10^5,a_i≠0) a1,a2,…,an(−105≤ai≤105,ai=0) 表示节点的权值。
保证一定有解。
输入样例:
2
5
1 2
1 3
3 4
3 5
3
1 2
1 3
输出样例:
-3 5 1 2 2
1 1 1
直接上结论:
把所有点按层数染色为黑色和红色,若为红色,每个节点的权值为其的度数,若为红色,若为黑色,每个节点的权值为其度数的相反数(太神仙了这个构造)。
证明:
由于是按层进行染色,所以一个红色节点一定连接一个黑色节点,即红色节点的度数和 d r d_r dr 等于黑色节点的度数和 d b d_b db,而红色节点的权值和为其度数和 d r d_r dr,黑色节点的权值和为其度数和的相反数 − d b -d_b −db,因此整棵树的权值为 0 0 0。
接下来考虑任意一棵子树的情况:
其实红色和黑色只用讨论一种即可(我们这边讨论左边的情况)。
倘若不存在蓝色边,那么左边黑色子树所有节点的权值为 0 0 0(已证),那么当加上蓝色边后,由于黑色子树连接的节点为红色,因此黑色子树的根节点应该为黑色,由于加上蓝色边只使得黑色子树的根节点度数加一,因此仅会影响黑色子树根节点的权值,对于黑色子树的其他节点没有影响,所以黑色子树的节点的权值和为 0 + ( − 1 ) = − 1 0+(-1)=-1 0+(−1)=−1,同理右边情况红色子树节点权值和为 1 1 1。
当去掉一个节点时,由于该节点的颜色是确定的,因此与其连接的节点的颜色也是确定的(若去掉的节点为红色,则所有与之相连的节点为黑色,反之亦然),因此与其连接的子树的权值和是确定的,要么为 1 1 1,要么为 − 1 -1 −1,所以去掉该节点后剩下的连通块(即子树)的权值即为确定的。
当然不想看证明的模拟一下就行了,而且真实比赛时也不可能想到这种证明的。
下面放上样例的模拟(绿色为权值):
#include
// #define int long long
using namespace std;
constexpr int P = 998244353;
using i64 = long long;
void solve()
{
int n;
cin >> n;
vector<vector<int>> a(n);
vector<int> d(n);
for (int i = 0; i < n - 1; i ++ ) {
int u, v;
cin >> u >> v;
u -- ;
v -- ;
d[u] ++ ;
d[v] ++ ;
a[u].push_back(v);
a[v].push_back(u);
}
vector<int> ans(n);
// u为当前遍历节点,fa为u的父亲,主要是防止往回搜,t为节点类型,方便赋权值
function<void(int, int, int)> dfs = [&](int u, int fa, int t) {
ans[u] = t * d[u]; // 权值由度数和颜色决定
for (int i = 0; i < (int)a[u].size(); i ++ ) {
int v = a[u][i];
if (v == fa) continue;
dfs(v, u, -t);
}
};
dfs(0, -1, 1);
for (int i = 0; i < n; i ++ ) cout << ans[i] << " ";
cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T -- ) {
solve();
}
return 0;
}