Codeforces Round #377 (Div. 2)
题目链接:https://codeforces.com/contest/732
A - Buy a Shovel
题意:有无穷枚10元硬币和1枚r元硬币,且r是[1,9]。每个物品的单价是x元,求最少买多少件不用找钱。
题解:记得区分用r元和不用r元两种情况,分别对应模10的余数为r或者为0。现在枚举不超过10个就可以出结果。
B - Cormen --- The Best Friend Of a Man
题意:宠物狗每连续两天都需要总共走至少k步,注意对于[0,1]和[n,n+1]没有这个要求。给出一个数列表示第几天走几步,增加最少的步使得[1,2],[2,3]...[n-1,n]都满足每连续两天都需要总共走至少k步。
题解:贪心往右边的一天放。注意最后一天。
C - Sanatorium
题意:某人在食堂待了连续的一段时间,而每一天都依次吃早餐b、晚餐d、宵夜s,而他有可能会漏掉其中的一些餐。已知三种餐吃的次数,求最少漏了多少餐。
题解:可以分多种情况讨论,得到一个贪心:
假设他是从b开始吃,最后一餐吃b,则b会比另外两种多1。
假设他是从b开始吃,最后一餐吃d,则b和d会比s多1。
假设……
最后会发现一个规律,要把最少的两种都配到最多比最多的一种少1,而和他们的顺序无关(三种是完全对称的)。
*D - Exams
挺有意思的一道题。
题意:一段连续的日子,每个日子可能会有恰好一门课的Exam,或者没有任何Exam。问这段日子是否可以成功通过所有课的考试。注意每门课可能有多次Exam,只需要选一门完成。要是不参加当天的考试,则可以用来复习。第j门课在考试前需要aj天复习,问最短的通过所有考试的时间,或说明无解。
题解:一开始还在想怎么用堆(延迟决策贪心)来贪心,但是显然有个二分的算法,二分天数x,则每门课肯定选x及其之前的最后一次考试来考,这个可以O(n)得到,然后必定是贪心,到了选择的一门课的时候就要尽可能分配已有的复习资源给它,不够分配则GG。
int n, m;
int d[100005];
int a[100005];
pii lst[100005];
bool check(int len) {
for(int i = 1; i <= m; ++i)
lst[i] = {0, a[i]};
for(int i = 1; i <= len; ++i)
lst[d[i]].first = i;
for(int i = 1; i <= m; ++i) {
if(lst[i].first == 0)
return 0;
}
sort(lst + 1, lst + 1 + m);
int cnt0 = lst[1].first - 1;
for(int i = 1; i <= m; ++i) {
if(cnt0 < lst[i].second)
return 0;
else {
if(i == m)
return 1;
cnt0 -= lst[i].second;
cnt0 += (lst[i + 1].first - lst[i].first - 1);
}
}
return 0;
}
int LB;
int bs() {
int L = LB, R = n;
while(1) {
int M = (L + R) >> 1;
if(L == M) {
if(check(L))
return L;
if(check(R))
return R;
return -1;
}
if(check(M))
R = M;
else
L = M + 1;
}
}
bool vis[100005];
void test_case() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%d", &d[i]);
int cnt = 0;
LB = -1;
for(int i = 1; i <= n; ++i) {
if(!vis[d[i]]) {
vis[d[i]] = 1;
++cnt;
if(cnt == m) {
LB = i;
break;
}
}
}
if(LB == -1) {
puts("-1");
return;
}
for(int i = 1; i <= m; ++i)
scanf("%d", &a[i]);
printf("%d\n", bs());
}
*E - Sockets
题意:有 \(n(1\leq n \leq 2\cdot 10^5)\) 电脑, \(m(1\leq m \leq 2\cdot 10^5)\) 个插座,其中每个电脑和插座都有自己唯一的功率。然后可以花一个适配器把一个插座的功率变成它的一半的上整(为什么是上整,不是很明白)。匹配尽可能多的电脑,在此前提上使用尽可能少的适配器。
题解:比赛时有个假算法,感觉比题目要求的复杂度略高。把所有电脑和插座都放在map里,键是功率,值是序号数组。显然在可以匹配的时候就匹配肯定是最优的,因为留着电脑只会在下次匹配多用适配器或者直接失配。所以每次把插座的值都减半就可以了,总共的复杂度应该是 \(O(nlognlogMaxPower)\) ,写了一发居然MLE了,说明map开内存实在玄学,所以后面改成值就不保存序号数组了,改为保存代表序号数组的地址。
但是按道理把这俩直接排序之后,可以直接双指针扫描完成匹配,然后再扫一遍完成减半,还不需要再排序,所以总复杂是 \(O(nlogMaxPower+nlogn)\) 。
内存和时间都差点爆了。
stack<int> nxtA[200005];
stack<int> nxtB[200005];
map<int, int> A;
map<int, int> B;
map<int, int> TB;
int C[200005];
int D[200005];
void test_case() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1, a; i <= n; ++i) {
scanf("%d", &a);
if(!A.count(a))
A[a] = i;
nxtA[A[a]].push(i);
}
for(int j = 1, b; j <= m; ++j) {
scanf("%d", &b);
if(!B.count(b))
B[b] = j;
nxtB[B[b]].push(j);
}
int cnt = 0, sum = 0;
int cost = 0;
int t = 31;
while(t--) {
for(auto &i : B) {
auto it = A.find(i.first);
if(it == A.end())
continue;
else {
stack<int> &sB = nxtB[i.second];
stack<int> &sA = nxtA[it->second];
while(sA.size() && sB.size()) {
int u = sB.top();
int v = sA.top();
C[u] = cost;
D[v] = u;
sum += cost;
++cnt;
sB.pop();
sA.pop();
}
}
}
++cost;
for(auto &i : B) {
if(nxtB[i.second].size() == 0)
continue;
auto it = TB.find((i.first + 1) / 2);
if(it == TB.end())
TB[(i.first + 1) / 2] = i.second;
else {
stack<int> &sB = nxtB[i.second];
int v = it->second;
while(sB.size()) {
nxtB[v].push(sB.top());
sB.pop();
}
}
}
B.swap(TB);
TB.clear();
}
printf("%d %d\n", cnt, sum);
for(int j = 1; j <= m; ++j)
printf("%d%c", C[j], " \n"[j == m]);
for(int i = 1; i <= n; ++i)
printf("%d%c", D[i], " \n"[i == n]);
}
重新写一个好点的。突然发现反正复杂度都是对的,甚至没有必要删除元素,直接标记为删除就可以了。
int n, m;
struct AB {
int val;
int idx;
int vis;
} A[200005], B[200005];
bool cmpval(const AB &c1, const AB &c2) {
return c1.val < c2.val;
}
bool cmpidx(const AB &c1, const AB &c2) {
return c1.idx < c2.idx;
}
void test_case() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) {
scanf("%d", &A[i].val);
A[i].idx = i;
A[i].vis = 0;
}
for(int j = 1; j <= m; ++j) {
scanf("%d", &B[j].val);
B[j].idx = j;
B[j].vis = 0;
}
sort(A + 1, A + 1 + n, cmpval);
sort(B + 1, B + 1 + m, cmpval);
int suc = 0, sum = 0, cnt = 0;
while(cnt <= 30) {
int i = 1, j = 1;
while(1) {
while(i <= n && A[i].vis != 0)
++i;
while(j <= m && B[j].vis != 0)
++j;
if(i > n || j > m)
break;
if(A[i].val == B[j].val) {
sum += cnt;
A[i].vis = B[j].idx;
B[j].vis = A[i].idx;
B[j].val = cnt;
++suc;
++i;
++j;
} else {
if(A[i].val > B[j].val)
++j;
else
++i;
}
}
for(int j = 1; j <= m; ++j) {
if(B[j].vis == 0)
B[j].val = (B[j].val + 1) / 2;
}
++cnt;
}
sort(A + 1, A + 1 + n, cmpidx);
sort(B + 1, B + 1 + m, cmpidx);
printf("%d %d\n", suc, sum);
for(int j = 1; j <= m; ++j)
printf("%d%c", B[j].vis ? B[j].val : 0, " \n"[j == m]);
for(int i = 1; i <= n; ++i)
printf("%d%c", A[i].vis, " \n"[i == n]);
}
收获:要注意vis的值不能直接用来记录用了多少个,因为有些是用0个的。需要做一点修改。还有就是注意区分排序后的下标和原下标,题目要的肯定是原下标。