链接:https://codeforces.com/contest/1305
来源:codeforces
文章目录
A. Kuroni and the Gifts(贪心)
思路:题目要求我们从两个数组中各挑一个数字求和(选过的不能再选),每个人都能得到这样一个数字。问最后每个人取得数字是什么。由于数组中的数字都各不相同,我们对数组进行排序,第一个人就选 a [ 1 ] a[1] a[1] 和 b [ 1 ] b[1] b[1],后面类似,这样每个人得到的数字必然不会相同。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e2 + 10;
int a[maxn], b[maxn];
int main(){
int T; scanf("%d",&T);
while(T -- ) {
int n; scanf("%d",&n);
for (int i = 0; i < n; i ++) scanf("%d", &a[i]);
for (int i = 0; i < n; i ++) scanf("%d", &b[i]);
sort(a, a + n);
sort(b, b + n);
for (int i = 0; i < n; i ++) printf("%d%c", a[i], i == n - 1? '\n' : ' ');
for (int i = 0; i < n; i ++) printf("%d%c", b[i], i == n - 1? '\n' : ' ');
}
return 0;
}
B. Kuroni and Simple Strings(枚举)
思路:题目的意思是让我们在给出的序列中删除类似于 ( ) () () ( ( ) ) (()) (()) ( ( ( ) ) ) ((())) ((())) 这样的简单括号序列(左边都是 ′ ( ′ '(' ′(′,右边都是 ′ ) ′ ')' ′)′),使得最后剩下的括号序列不是简单序列,但是可以为空。我们可以对字符串进行遍历,正向遍历判断当前字符前面有多少 ′ ( ′ '(' ′(′,反向遍历记录当前字符后面有多少 ′ ) ′ ')' ′)′,我们我们在遍历记录的这些值找到我们最多可以删除的子序列的长度 l e n len len 的位置。该位置左边我们从 1 1 1 开始删除 l e n / 2 len / 2 len/2 个,对于右边的 ‘ ) ’ ‘)’ ‘)’,我们也要删除相同的个数。但是此时我们需要从 n n n 位置开始删除。因为该位置某一边的 ′ ( ′ '(' ′(′ 或者 ′ ) ′ ')' ′)′ 此时一定被全部删除,另外一边如果是从两端删除的话,必然保证剩下的不是简单序列 e g : ( ( ) ( ) ) eg:(()()) eg:(()()) 我们找到了 2 2 2 号位置左右两边符合条件各两个,左边我们删除的顶端后面就有 ‘ ( ’ 或 者 ‘ ) ’ ‘(’或者‘)’ ‘(’或者‘)’ 被剩下,右边我们从右端点开始删除,那个这个位置后面一定会有剩余的 ‘ ( ’ 或 者 ‘ ) ’ ‘(’或者‘)’ ‘(’或者‘)’会剩下,这样就不会有简单序列
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int a[maxn], ans[maxn];
set<int>s;
map<int, int>m;
int main() {
int n, mod; scanf("%d%d", &n, &mod);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++) {
s.insert(a[i]);
m[a[i]]++;
if(m[a[i]] == 2) return puts("0"), 0;
}
if(s.size() > 1000) return puts("0"), 0;
int cnt = 0;
ll res = 1;
for(auto xxx : s) ans[cnt++] = xxx;
//for(int i=0;i<cnt;i++) cout<<ans[i]<<" "; cout<<endl;
for(int i = 0; i < cnt; i++) {
for(int j = i + 1; j < cnt; j++) {
res = res * abs(ans[i] - ans[j]) % mod;
}
}
printf("%lld\n", res);
return 0;
}
C. Kuroni and Impossible Calculation(抽屉原理)
思路:题意是求数组中所有的差相乘取模后的结果。首先我们可以知道如果某一个数出现次数超过了两次那么差中一定含有 0 0 0,此时结果一定是 0 0 0。其次我们考虑如果不同的数字超过了 1000 1000 1000 个。模数最大为 1000 1000 1000,如果刚好是 1000 1000 1000 个数,那么任何的模数都不会相同,超过之后必然有两个数的模数是相同的,那么他们的差取模之后必然为 0 0 0,剩下 1000 1000 1000 以内的结果我们就可以暴力求解了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int a[maxn], ans[maxn];
set<int>s;
map<int, int>m;
int main() {
int n, mod; scanf("%d%d", &n, &mod);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++) {
s.insert(a[i]);
m[a[i]]++;
if(m[a[i]] == 2) return puts("0"), 0;
}
if(s.size() > 1000) return puts("0"), 0;
int cnt = 0;
ll res = 1;
for(auto xxx : s) ans[cnt++] = xxx;
//for(int i=0;i<cnt;i++) cout<<ans[i]<<" "; cout<<endl;
for(int i = 0; i < cnt; i++) {
for(int j = i + 1; j < cnt; j++) {
res = res * abs(ans[i] - ans[j]) % mod;
}
}
printf("%lld\n", res);
return 0;
}
D. Kuroni and the Celebration(思维 + bfs)
思路:题意是让我们根据 l c a lca lca 找到这个树的根结点,其中 l c a lca lca 不用我们自己写。我们在存储树的的时候顺便记录每个结点的度。然后我们可以把度为 1 1 1 的结点放入队列中,每次取出两个元素判断其 l c a lca lca,如果符合条件则输出,否则删除这两个结点,更新其他结点的度,继续判断直到队列中的结点数少于两个为止。如果最后没有正确答案,说明队列中还剩下一个元素,这个元素就是根结点。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
vector<int>G[maxn];
vector<int>deg(maxn, 0);
int n;
int lac(int u, int v) {
printf("? %d %d\n", u, v);
fflush(stdout);
int lcaRes; scanf("%d", &lcaRes);
return lcaRes;
}
void bfs() {
queue<int>q;
for (int i = 1; i <= n; i ++) {
for (auto xxx : G[i]) {
if (deg[xxx] == 1) q.push(xxx);
}
}
while(q.size() >= 2) {
int u = q.front(); q.pop();
int v = q.front(); q.pop();
int lcaAns = lac(u, v);
if (lcaAns == u || lcaAns == v) {
printf("! %d\n", lcaAns);
exit(0);
}
for (auto xxx : G[u]) {
if (-- deg[xxx] == 1) q.push(xxx);
}
for (auto xxx : G[v]) {
if (-- deg[xxx] == 1) q.push(xxx);
}
}
printf("! %d\n", q.front());
}
int main() {
scanf("%d", &n);
for(int i = 1; i < n; i ++) {
int u, v; scanf("%d%d", &u, &v);
G[u].push_back(v); G[v].push_back(u);
deg[u] ++; deg[v] ++;
}
bfs();
return 0;
}
E. Kuroni and the Score Distribution(贪心 + 思维)
思路:对于 1 , 2 , 3 , 4.... 1,2,3,4.... 1,2,3,4.... 这样的等差数列我们来分析符合条件的个数。对于 1,2 没有符合条件的,对于 3,前面两个数中符合条件的数对有 ( 3 − 1 ) / 2 , 4 (3 - 1) / 2,4 (3−1)/2,4 就有 ( 4 − 1 ) / 2 (4 - 1) / 2 (4−1)/2 对,一次类推就可以找到个数的规律。首先我们先按照这样的序列取构造一个序列,在构造序列的过程中我们同时记录满足题意的数对的个数。当到达第一个数时,如果发现当前的个数大于 > = m >= m >=m,那么就有 c n t − m cnt - m cnt−m 对个数(也就是 ( c n t − m ) ∗ 2 ) (cnt - m) * 2) (cnt−m)∗2) 我们应该不让他构成符合题意的数对。也就是对于前 ( c n t − m ) ∗ 2 ) (cnt - m) * 2) (cnt−m)∗2)个数我们不让他构成合法的数列,那么当前的值我们就应该加上 ( c n t − m ) ∗ 2 ) (cnt - m) * 2) (cnt−m)∗2) ,是的前面的那些数无论怎么组合都不能等于当前的值。此时次数我们就凑够了,后面的数字我们就可以用 1 e 9 1e9 1e9 之前的数字来填充,我们不能以 1,2,3这样的差值来填充,因为如果用这些差值来填充,得到的数字加上前面的数字就能构成满足题意的数对。所以此时的差值我设置成了 i + 1 i + 1 i+1。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 10;
int a[maxn];
int main() {
int n, m; scanf("%d%d", &n, &m);
int cnt = 0;
for (int i = 1; i <= n; i ++) {
a[i] = i; cnt += (i - 1) >> 1;
if (cnt >= m) {
a[i] += (cnt - m) << 1;
for (int j = n, k = 1e9; j > i; j --, k -= i + 1) {
a[j] = k;
}
for (int j = 1; j <= n; j ++) {
printf("%d ", a[j]);
}
return 0;
}
}
puts("-1");
return 0;
}