Codeforces Round #617 (Div. 3)
题目链接:https://codeforces.com/contest/1296
好菜啊,只会写一下水题。
A - Array with Odd Sum
题意:给一个序列 \(a_i\) ,每次操作可以选两个数,把其中一个赋值为另一个。可以进行任意次操作。问能不能把这个序列改成加法和为奇数。
题解:首先加法和本身是奇数就不用改,否则和一定是偶数。假如全是奇数或者全是偶数,那么操作的赋值不会改变和的奇偶性,否则可以把一个奇数赋值为偶数或者反过来,让和的奇偶性改变。
void test_case() {
int n;
scanf("%d", &n);
int ans = 0, sum = 0;;
for(int i = 1, x; i <= n; ++i) {
scanf("%d", &x);
sum += x;
if(x & 1)
ans |= 1;
else
ans |= 2;
}
if(sum & 1 == 1 || ans == 3)
puts("YES");
else
puts("NO");
}
B - Food Buying
题意:有n个硬币,然后有一个优惠活动,就是每买10块钱返现1块钱,已知可以买任意价格的物品,求n块钱可以买多少钱的东西。
题解:模拟,每满10就返还10,那么每次都用10的倍数来使得重复利用这个优惠活动。每次买东西大致让硬币数变为原来的1/10。
void test_case() {
int n;
scanf("%d", &n);
ll sum = 0;
while(n >= 10) {
sum += n / 10 * 10;
n = n % 10 + n / 10;
}
sum += n;
printf("%lld\n", sum);
}
C - Yet Another Walking Robot
题意:给一个上下左右移动的路径,去除其中最小长度的一段非空路径,要使得路径的终点不变。
题解:终点不变,相当于去除的一个回路。搞个map记录上次来到这个点是哪一步,这样再次来到同一个点的时候就知道这个回路的长度。
int n, l, r, anslen;
char s[200005];
map<int, int> vis[400005];
void test_case() {
scanf("%d%s", &n, s + 1);
for(int i = 0; i <= n; ++i) {
vis[200000 + i].clear();
vis[200000 - i].clear();
}
int cx = 200000, cy = 0;
anslen = INF;
vis[cx][cy] = 0;
for(int i = 1; i <= n; ++i) {
if(s[i] == 'L')
--cx;
else if(s[i] == 'R')
++cx;
else if(s[i] == 'D')
--cy;
else
++cy;
if(vis[cx].count(cy)) {
int L = vis[cx][cy] + 1;
int R = i;
if(R - L + 1 < anslen) {
anslen = R - L + 1;
l = L;
r = R;
}
}
vis[cx][cy] = i;
}
if(anslen != INF)
printf("%d %d\n", l, r);
else
puts("-1");
}
D - Fight with Monsters
题意:你和基友去打怪。一共有 \(n\) 个怪物,这些怪物很惨都不会还手,所以只需要看怪物的hp,定义一个怪物被打爆,当其生命值小于等于0,假如是你完成补刀那么你会获得1分,否则若是基友补刀你不会得分。打怪的规则是,对于每一个怪物,你和基友轮流揍这个怪物,而且你一定是先动手的。你一次可以打怪物a点hp,你基友一次可以打怪物b点hp。你可以有奇怪的操作(砸瓦鲁多)让你的基友跳过回合从而达到对怪物进行连击,求至多使用 \(k\) 次操作你能够获得的最大得分。
题解:首先一个很明显的贪心,就是要么不用操作,要么一直用操作直到自己补刀完成。其次假如两个人轮流一人打一下不会让怪物被打爆就可以让怪物被一人打一下,所以每个怪物的hp可以直接mod(a+b),注意假如得到的结果为0意味着最后一刀已经被基友补了,所以要加回去变成(a+b)。然后每个怪物都处在一种再被一人打一下一定爆了的状态,这个时候自己无论如何都要先打一下,所以<=a的hp的怪物直接爆了。然后剩下的一定要用砸瓦鲁多来连击怪物,需要连击的数量必定是除以a的上整。然后根据第一句话的贪心优先选需要砸瓦鲁多最少的怪物来补刀。
int n, a, b, k;
int h[200005];
int c[200005];
void test_case() {
scanf("%d%d%d%d", &n, &a, &b, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d", &h[i]);
h[i] %= (a + b);
if(h[i] == 0)
c[i] = (b + a - 1) / a;
else
c[i] = (h[i] - 1) / a;
//printf("c[%d]=%d\n", i, c[i]);
}
sort(c + 1, c + 1 + n);
int ans = 0, res = k;
for(int i = 1; i <= n; ++i) {
if(res >= c[i]) {
++ans;
res -= c[i];
} else
break;
}
printf("%d\n", ans);
}
E1 - String Coloring (easy version)
题意:给一个小写字母字符串,要求对其进行排序。给每个字母(不是每种字母,意思是同种字母的不同个体可以染不同的颜色)染上黑色或者白色。每次只能进行临位交换,且只能把黑白颜色不同的字母交换。求是否可行,若可行则给出一种染色构造。
题解:也就是逆序对的两个元素要染成不同的颜色才能够正确的换过来。假如出现了cba这种逆序,就一定无解,否则至多是cab这种逆序,这样就把后面的两个染为1即可。(假如是dabc就把后面三个染为1即可)
*E2 - String Coloring (hard version)
想了一个假做法,肯定是同一种字母染同一种颜色,然后尝试合并一些不需要互相穿过的字母。这样的错误在于不知道怎么合并才是最优的。
nb群友说是什么LIS,什么最长反链,也就是导弹拦截器那道题,那怪不得我不会了,我从大一开始就没懂过这个。不过好像上一次有见过一个CF要用这个Dilworth定理。
Dilworth定理:对于一个偏序集,最少链划分等于最长反链长度。
Dilworth定理的对偶定理:对于一个偏序集,其最少反链划分数等于其最长链的长度。
复习一下这个:设R是集合A上的一个关系,如果R是自反的、反对称的和可传递的,则称R是集合A的偏序关系。(比如 \(\leq\) )
自反的:集合A中任意元素a,有a关系R于a恒成立。
反对称的:集合A中,若有a关系R于b,且b关系R于a,则a=b恒成立。
可传递的:集合A中,若有a关系R于b,且b关系R于c,则a关系R于c恒成立。
看见这种离散数学类的证明我头都晕了,我当时是心有多大才选的读计算机。
我好像有点懂什么Dilworth定理了!虽然这场被学弟打爆了(遇到没有数据结构的还很多离散数学的暴毙场直接暴毙非常正常,看来要恶补离散数学和图论(这东西好像和Hasse图有关))
题解:一个不下降的子序列,构成一条链,可以把整个序列划分成若干个不下降子序列。同一个不降子序列之中的元素之间没有必要互相穿过,所以可以染同一种颜色。而染不同颜色的几个子序列之间当然可以互相交换顺序。这样构造的方法已经给出了,只需要证明这是最少的染色数。而由于最长下降子序列的存在的原因,颜色数不会少于最长下降子序列的长度(也是E1中观察的结果)。由Dilworth定理知,最长下降子序列的长度等于最小的不下降子序列的链划分,所以这是正确的构造。
int n;
char s[200005];
int A[200005];
int B[200005];
int id[200005];
int C[200005];
int LIS() {
int len = 1;
B[1] = A[1];
C[1] = 1;
id[1] = 1;
for(int i = 2; i <= n; ++i) {
if(A[i] > B[len]) {
B[++len] = A[i];
C[i] = len;
id[len] = i;
} else {
int pos = lower_bound(B + 1, B + 1 + len, A[i]) - B;
B[pos] = A[i];
C[i] = C[id[pos]];
id[pos] = i;
}
}
return len;
}
void test_case() {
scanf("%d%s", &n, s + 1);
for(int i = 1; i <= n; ++i)
A[i] = 'd' + 1 - s[i];
int len = LIS();
printf("%d\n", len);
for(int i = 1; i <= n; ++i)
printf("%d%c", C[i], " \n"[i == n]);
}
*F - Berland Beauty
题意:给一棵n(<=5000)个点的无向树,然后有m个限制,每个限制都是“路径(u,v)上的最小值为w”,构造一棵树满足这样的限制。
题解:由于n比较小所以修改的时候可以暴力。先考虑怎么构造,其实只需要把路上的小于w的值全部修改为w,这样就尽最大可能满足题意了。当然如果路上的值都大于w就说明已经矛盾了。然后要再遍历一次来确认原本最小值修改为w的路上还至少留着1个w。剩下的边是无所谓的,随便整一个值。
那么怎么找(u,v)的路径呢?Mike给的方法是按每个点分别为根dfs一次,然后就知道定根以后的各节点父亲是谁,那么从u到v的路,只需要v一直上升到u就可以。或者更简单,像找lca一样不断提升深度大的节点就可以了。直到两个点重合,而不是他们的父亲重合,注意是包含到lca的边的
vector<pii> G[5005];
int P[5005];
int D[5005];
int W[5005];
int ID[5005];
int ans[5005];
struct Node {
int u, v, w;
bool operator<(const Node &n)const {
return w > n.w;
}
} nd[5005];
void dfs(int u, int p, int d, int id) {
D[u] = d;
P[u] = p;
ID[u] = id;
W[u] = 1;
ans[ID[u]] = 1;
for(auto &e : G[u]) {
if(e.first == p)
continue;
dfs(e.first, u, d + 1, e.second);
}
}
bool solve(int id) {
int u = nd[id].u;
int v = nd[id].v;
int w = nd[id].w;
while(u != v) {
if(D[u] >= D[v]) {
W[u] = max(W[u], w);
ans[ID[u]] = W[u];
u = P[u];
} else {
W[v] = max(W[v], w);
ans[ID[v]] = W[v];
v = P[v];
}
}
return 1;
}
bool check(int id) {
int u = nd[id].u;
int v = nd[id].v;
int w = nd[id].w;
while(u != v) {
if(D[u] >= D[v]) {
if(W[u] == w)
return 1;
u = P[u];
} else {
if(W[v] == w)
return 1;
v = P[v];
}
}
return 0;
}
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back({v, i});
G[v].push_back({u, i});
}
dfs(1, -1, 0, -1);
int m;
scanf("%d", &m);
for(int i = 1; i <= m; ++i)
scanf("%d%d%d", &nd[i].u, &nd[i].v, &nd[i].w);
sort(nd + 1, nd + 1 + m);
int suc = 1;
for(int i = 1; i <= m; ++i) {
if(!solve(i)) {
suc = 0;
break;
}
}
if(suc) {
for(int i = 1; i <= m; ++i) {
if(!check(i)) {
suc = 0;
break;
}
}
}
if(suc == 0)
puts("-1");
else {
for(int i = 1; i <= n - 1; ++i)
printf("%d%c", ans[i], " \n"[i == n - 1]);
}
}