美团点评CodeM资格赛部分问题题解
优惠券
注意的点是任何时刻一张优惠券只能存在一个,如果同一张优惠券还没被使用又购入,就看上一次购入与该次购入之间有没有问号可以替代使用,使用优惠券同理。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <set>
using namespace std;
int len;
int main()
{
string line;
mywhile:
while (cin >> len) {
getchar();
unordered_set<string> cards;
set<int> unknowns;
unordered_map<string, int> last_out, last_in;
for (int i = 1; i <= len; ++i) {
getline(cin, line);
if (line[0] == 'I') {
string x = line.substr(2);
if (cards.count(x) == 0) {
cards.insert(x);
} else {
int last_t = ( last_in.count(x) == 1 ? last_in[x] : 0 );
set<int>::iterator it = unknowns.lower_bound(last_t);
if (it == unknowns.end()) {
cout << i << endl;
goto mywhile;
} else {
unknowns.erase(it);
}
}
last_in[x] = i;
} else if (line[0] == 'O') {
string x = line.substr(2);
if (cards.count(x) == 1) {
cards.erase(x);
} else {
int last_t = ( last_out.count(x) == 1 ? last_out[x] : 0 );
set<int>::iterator it = unknowns.lower_bound(last_t);
if (it == unknowns.end()) {
cout << i << endl;
goto mywhile;
} else {
unknowns.erase(it);
}
}
last_out[x] = i;
} else if (line == "?") {
unknowns.insert(i);
}
}
cout << -1 << endl;
}
return 0;
}
送外卖
https://www.nowcoder.com/question/next?pid=5513596&qid=104903&tid=8742003
挺好的题。我的做法是建图——将每个小区抽象为图节点,小区i
有两条出边,出边a
到达小区i+a[i]
,出边b
到达小区i+b[i]
(如果到达的小区不存在就不用加边)。问题即转化为在一个有向图中,判断节点0
与n-1
是否可达,如果可达,每次尽量走边a
(这样字典序才是最小的)。
- 预处理好每个节点到
n-1
是否可达的信息,这样在构造路径的时候好快速选择正确的下一跳。为此,在建图的时候同时记录某个节点的前继节点,然后从节点n-1
出发跑一遍BFS即可。 - 从起点
0
出发构造路径:- 如果当前节点已经是终点了,返回结果;
- 否则看走
a
到达的下一跳与终点是否可达,是的话就走a
,否则走b
; - 如果选择的下一跳已经被走过,说明构成了一个环,这意味着字典序最小的字符串无限长。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <queue>
using namespace std;
int n;
void solve(vector<int>& a, vector<int>& b, vector<vector<bool>>& adj, vector<vector<int>>& prevs)
{
int s = 0, t = n - 1;
vector<bool> can(n, false);
can[n-1] = true;
// BFS
queue<int> q;
q.push(t);
while (!q.empty()) {
int cur = q.front(); q.pop();
for (int i = 0; i < prevs[cur].size(); ++i) {
if (!can[prevs[cur][i]]) {
can[prevs[cur][i]] = true;
q.push( prevs[cur][i] );
}
}
}
// go from s
int cur = s;
if (!can[cur]) {
cout << "No solution!" << endl;
return;
}
vector<bool> vis(n, false);
vis[cur] = true;
string res = "";
while (cur != t) {
for (int i = 0; i < adj[cur].size(); ++i) {
//pair<char, int> p = adj[cur][i];
bool select = adj[cur][i];
int to;
char select_;
if (select == true) {
to = cur + a[cur];
select_ = 'a';
} else {
to = cur + b[cur];
select_ = 'b';
}
if (to == t) {
res += select_;
cout << res << endl;
return;
}
if (can[to]) {
if (vis[to]) {
cout << "Infinity!" << endl;
return;
}
vis[to] = true; // 一开始由于这句没加上,导致爆空间,浪费了四次提交机会。。
res += select_;
cur = to;
break;
}
}
}
}
int main()
{
cin >> n;
vector<int> a(n);
vector<int> b(n);
vector<vector<bool>> adj(n);
vector<vector<int>> prevs(n);
for (int i = 0; i < n; ++i) {
scanf("%d", &a[i]);
int to = i + a[i];
if (to >= 0 && to < n) {
adj[i].push_back(true);
prevs[to].push_back(i);
}
}
for (int i = 0; i < n; ++i) {
scanf("%d", &b[i]);
int to = i + b[i];
if (to >= 0 && to < n) {
adj[i].push_back(false);
prevs[to].push_back(i);
}
}
solve(a, b, adj, prevs);
return 0;
}
数码
https://www.nowcoder.com/question/next?pid=5513596&qid=104907&tid=8742003
- 首先,计算
1~n
的结果,则区间[l, r]
的结果等于[1, r]
减去[1, l-1]
- 核心是计算最高位为
i (1 <= i <= 9)
的约数在[1, n]
出现的次数 - 以
i = 2
为例,ans[2] = cnt(2) + cnt(20) + cnt(21) + ... + cnt(29) + ... + cnt(200) + cnt(201) + ... + cnt(不超过n的最高位为2的最大的数)
, 其中cnt(x)
表示[1,n]
中约数为某个数x
的数量,它很好计算:cnt(x) = n / x
- 那么最高位为
i
时,分别统计长度为1,2,...的数:长度为1是[i,(i+1)-1]
; 长度为2是[i*10, (i+1)*10-1]
; 长度为3是[i*10^2, (i+1)*10^2-1]
,以此类推,长度为j
是[i*10^(j-1), (i+1)*10^(j-1)-1]
- 于是
ans[i] = sum(cnt(x) for x in range(i, i+1)) + sum(cnt(x) for x in range(i*10, (i+1)*10)) + sum(cnt(x) for x in range(i*10^2, (i+1)*10^2)) + ...
- 但是当n很大时(比如
n >= 10^7
),枚举量会很大 - 长度为
j
时的数量为sum(cnt(x) for x in range(i*10^(j-1)), (i+1)*10^(j-1)) = n/low + n/(low+1) + ... + n/high
,其中low = i*10^(j-1), high = (i+1)*10^(j-1) - 1
. 注意到随着x
的增加,n / x
是单调不增的,且当j >= 6
时,最大的项n/low = n / (i*10^(j-1)) <= n / (i*10^5) <= 10^4 / i <= 10^4
,可见这么多项的值是不多的(都是离散量),最多也就10000个,这就意味着我们可以换个思路-->去枚举每一项值的范围,然后在{n/low, n/(low+1) ... n/high}
中找等于该值的项的数量是多少。至于如何找,其实对每一个值可以O(1)找出来,如果对细节处理没把握,保险起见还可以二分查找-->由于这个数列是单调的,分别找到等于这个值的最左边的项和最右边的项即可
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <queue>
using namespace std;
long long l, r;
long long compute_single(long long low, long long high, long long n)
{
// compute: n/low + n/(low+1) + ... + n/(high)
if (low > high) return 0;
long long ret = 0;
if (high - low <= 1e5) {
for (long long i = low; i <= high; ++i) {
ret += (n / i);
}
} else {
long long start = n / high, end = n / low;
for (long long x = start; x <= end; ++x) { // how many n/i equals to x
long long lb = 0, ub = -1;
// binary search for lb and ub
// lb
long long left = low, right = high, mid;
while (left + 1 < right) {
mid = left + (right - left) / 2;
if (n / mid <= x) right = mid;
else left = mid;
}
if (n / left == x) lb = left;
else if (n / right == x) lb = right;
// ub
left = low; right = high;
while (left + 1 < right) {
mid = left + (right - left) / 2;
if (n / mid >= x) left = mid;
else right = mid;
}
if (n / right == x) ub = right;
else if (n / left == x) ub = left;
//cout << lb << " " << ub << " " << x << endl;
ret += (ub - lb + 1) * x;
}
}
return ret;
}
void compute(long long n, vector<long long>& ans)
{
if (n == 0) return;
for (int i = 1; i <= 9; ++i) { // digit: i
long long base = 1;
for (int j = 0; j <= 9; ++j) { // j+1: the length of number with i the highest digit
long long low = i * base, high = (i + 1) * base - 1;
high = min(high, n);
long long cur = compute_single(low, high, n);
ans[i] += cur;
if (high >= n) break;
base *= 10;
}
}
}
void solve()
{
vector<long long> ans_l(10, 0);
vector<long long> ans_r(10, 0);
compute(r, ans_r);
compute(l-1, ans_l);
for (int i = 1; i <= 9; ++i) {
cout << (ans_r[i] - ans_l[i]) << endl;
}
}
int main()
{
cin >> l >> r;
solve();
return 0;
}