Educational Codeforces Round 6
题目链接:https://codeforces.com/contest/616
A - Professor GukiZ's Robot
送分题。
*B - Grandfather Dovlet’s calculator
题意:求L,R范围内的[0,9]的数字的出现次数。
这题好像可以数位dp,把10种数字分开统计。虽然这个数据范围没必要,但是想写一下这个解法。
数位dp解法:需要注意,当lead标记存在时,统一不统计0的出现次数(即使这个数就是0,也不计算,为了简化程序),然后就是巨复杂的limit标记。当limit标记存在且i==d[pos],则有后面的数位仍然受限,例如:123456,确定了123???,则???的范围是[000,456],要找十进制下低位余数+1。否则,例如123456,确定了122???,则???的范围是[000,999]。
int k;
ll p10[17];
int d[17];
ll r[17];
ll dp[17];
ll dfs(int pos, bool lead, bool limit) {
if(pos == 0)
return 0;
if(!lead && !limit && dp[pos] != -1)
return dp[pos];
int up = limit ? d[pos] : 9;
ll ans = 0;
for(int i = 0; i <= up; i++) {
ll D = dfs(pos - 1, lead && i == 0, limit && i == d[pos]);
ans += D;
if(i == k) {
if(lead && i == 0)
continue;
if(limit && i == d[pos])
ans += r[pos - 1] + 1;
else
ans += p10[pos - 1];
}
}
if(!lead && !limit)
dp[pos] = ans;
return ans;
}
ll solve(ll x) {
if(x < 0)
return 0;
int pos = 0;
ll cx = x;
while(cx) {
d[++pos] = cx % 10;
cx /= 10;
}
for(int i = 1; i <= pos; ++i)
r[i] = x % p10[i];
return dfs(pos, true, true);
}
int cost[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
void test_case() {
p10[0] = 1;
for(int i = 1; i < 17; ++i)
p10[i] = p10[i - 1] * 10;
ll L, R;
scanf("%lld%lld", &L, &R);
ll sum = 0;
for(k = 0; k <= 9; ++k) {
memset(dp, -1, sizeof(dp));
ll cnt = solve(R) - solve(L - 1);
sum += cnt * cost[k];
}
printf("%lld\n", sum);
}
暴力写法:
int cnt[10];
int cost[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
void test_case() {
int L, R;
scanf("%d%d", &L, &R);
for(int i = L; i <= R; ++i) {
int x = i;
while(x) {
++cnt[x % 10];
x /= 10;
}
}
ll sum = 0;
for(int k = 0; k <= 9; ++k)
sum += cnt[k] * cost[k];
printf("%lld\n", sum);
}
*C - Pearls in a Row
题意:给一个数组,切分成尽可能多的段,使得每一段都至少有两个相同的元素。
是个简单的贪心尺取,每次有两个相同的就切一段。注意最后一段没有被成功切下来的,不能独立成段,而是合并到最后一段成功切下来的段中。因为题目要求每一段都要是成功切下来的段。
离散化又搞错xtop和n。
int a[300005];
int x[300005], xtop;
int cnt[300005];
int L[300005];
int R[300005];
int top;
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
x[i] = a[i];
}
sort(x + 1, x + 1 + n);
xtop = unique(x + 1, x + 1 + n) - (x + 1);
for(int i = 1; i <= n; ++i)
a[i] = lower_bound(x + 1, x + 1 + xtop, a[i]) - x;
top = 0;
int l = 1;
for(int i = 1; i <= n; ++i) {
++cnt[a[i]];
if(cnt[a[i]] == 2) {
++top;
L[top] = l;
R[top] = i;
for(int j = l; j <= i; ++j)
cnt[a[j]] = 0;
l = i + 1;
}
}
if(top == 0) {
puts("-1");
return;
}
if(l <= n)
R[top] = n;
printf("%d\n", top);
for(int i = 1; i <= top; ++i)
printf("%d %d\n", L[i], R[i]);
}
好像set没有多慢,才比上面的慢50%。
int a[300005];
set<int> s;
int L[300005];
int R[300005];
int top;
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
top = 0;
int l = 1;
for(int i = 1; i <= n; ++i) {
if(s.count(a[i])) {
++top;
L[top] = l;
R[top] = i;
s.clear();
l = i + 1;
} else
s.insert(a[i]);
}
if(top == 0) {
puts("-1");
return;
}
if(l <= n)
R[top] = n;
printf("%d\n", top);
for(int i = 1; i <= top; ++i)
printf("%d %d\n", L[i], R[i]);
}
所以还是直接用set算了。
*D - Professor GukiZ and Two Arrays
题意:给n(<=2e3)个数字的数组a,和m(<=2e3)个数字的数组b。交换不超过2次,最小化数组a和数组b的和的差的绝对值。每次交换可以把一个数组a的元素和一个数组b的元素交换。
题解:交换0次的,可以直接一边扫出来,交换1次的,可以O(nm)枚举是哪一对发生了交换,立刻扫出来。交换2次的,易知选取的是两对不同的元素。看一下数据范围,考虑使用数据结构。枚举a中的所有的pair,把他们的和的两倍2(ai+aj)作为第一关键字插进数据结构Map中。然后再枚举b中所有的pair。假如一开始的和是sa和sb,那么bi+bj换出之后,要换入哪个ai+aj才是差的绝对值最小呢?易知新的b的和为sb'=sb-(bi+bj)+(ai+aj),新的a的和为sa'=sa-(ai+aj)+(bi+bj),差值d=|sa-sb-2(ai+aj)+2(bi+bj)|,要最小化这个,就应该使得sa-sb+2(bi+bj)与2(ai+aj)尽可能接近。换言之应该在Map中lower_bound找到第一个大于等于的。若等于则直接构造出差值为0的解,否则一定是大于,就把得到的迭代器和前一个迭代器都取出来看看。
Map的写法:
int n, m;
int a[2005], b[2005];
map<ll, pair<int, int> >Map;
void test_case() {
scanf("%d", &n);
ll sa = 0, sb = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
sa += a[i];
}
scanf("%d",&m);
for(int j = 1; j <= m; ++j) {
scanf("%d", &b[j]);
sb += b[j];
}
ll dis = abs(sa - sb);
if(dis == 0) {
puts("0");
puts("0");
return;
}
int k = 0, k1 = -1, k2 = -1, k3 = -1, k4 = -1;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
ll tmpdis = abs(sa - sb - 2ll * a[i] + 2ll * b[j]);
if(tmpdis == 0) {
puts("0");
puts("1");
printf("%d %d\n", i, j);
return;
}
if(tmpdis < dis) {
dis = tmpdis;
k = 1;
k1 = i;
k2 = j;
}
}
}
for(int i = 1; i <= n; ++i) {
for(int j = i + 1; j <= n; ++j)
Map[2ll * (a[i] + a[j])] = {i, j};
}
for(int i = 1; i <= m; ++i) {
for(int j = i + 1; j <= m; ++j) {
ll tmp = sa - sb + 2ll * (b[i] + b[j]);
auto it = Map.lower_bound(tmp);
if(it != Map.end()) {
ll tmpdis = abs(it->first - tmp);
if(tmpdis < dis) {
dis = tmpdis;
k = 2;
k1 = it->second.first;
k2 = i;
k3 = it->second.second;
k4 = j;
}
}
if(it != Map.begin()) {
--it;
ll tmpdis = abs(it->first - tmp);
if(tmpdis < dis) {
dis = tmpdis;
k = 2;
k1 = it->second.first;
k2 = i;
k3 = it->second.second;
k4 = j;
}
}
}
}
printf("%lld\n", dis);
printf("%d\n", k);
if(k == 0)
return;
if(k == 1) {
printf("%d %d\n", k1, k2);
return;
}
if(k == 2) {
printf("%d %d\n", k1, k2);
printf("%d %d\n", k3, k4);
return;
}
}
可能常数会好一点(3.5倍速)的写法:
int n, m;
int a[2005], b[2005];
pair<ll, pii> Map[4000005];
ll x[4000005];
void test_case() {
scanf("%d", &n);
ll sa = 0, sb = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
sa += a[i];
}
scanf("%d", &m);
for(int j = 1; j <= m; ++j) {
scanf("%d", &b[j]);
sb += b[j];
}
ll dis = abs(sa - sb);
if(dis == 0) {
puts("0");
puts("0");
return;
}
int k = 0, k1 = -1, k2 = -1, k3 = -1, k4 = -1;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
ll tmpdis = abs(sa - sb - 2ll * a[i] + 2ll * b[j]);
if(tmpdis == 0) {
puts("0");
puts("1");
printf("%d %d\n", i, j);
return;
}
if(tmpdis < dis) {
dis = tmpdis;
k = 1;
k1 = i;
k2 = j;
}
}
}
int Mtop = 0;
for(int i = 1; i <= n; ++i) {
for(int j = i + 1; j <= n; ++j)
Map[++Mtop] = make_pair(2ll * (a[i] + a[j]), make_pair(i, j));
}
sort(Map + 1, Map + 1 + Mtop);
for(int i = 1; i <= Mtop; ++i)
x[i] = Map[i].first;
for(int i = 1; i <= m; ++i) {
for(int j = i + 1; j <= m; ++j) {
ll tmp = sa - sb + 2ll * (b[i] + b[j]);
int pos = lower_bound(x + 1, x + 1 + Mtop, tmp) - x;
if(pos <= Mtop) {
ll tmpdis = abs(Map[pos].first - tmp);
if(tmpdis < dis) {
dis = tmpdis;
k = 2;
k1 = Map[pos].second.first;
k2 = i;
k3 = Map[pos].second.second;
k4 = j;
}
}
if(pos != 1) {
--pos;
ll tmpdis = abs(Map[pos].first - tmp);
if(tmpdis < dis) {
dis = tmpdis;
k = 2;
k1 = Map[pos].second.first;
k2 = i;
k3 = Map[pos].second.second;
k4 = j;
}
}
}
}
printf("%lld\n", dis);
printf("%d\n", k);
if(k == 0)
return;
if(k == 1) {
printf("%d %d\n", k1, k2);
return;
}
if(k == 2) {
printf("%d %d\n", k1, k2);
printf("%d %d\n", k3, k4);
return;
}
}
实验证明这个Map确实是有够慢的,假如不需要修改的话还是排序之后二分最实在。
*E - New Year Tree
看起来很线段树的一道题。
题意:一棵以1为根的数,每个点带有一种颜色。每次操作:操作1:把一棵v节点下属的子树的颜色全部改变成c;或者操作2:询问一棵v节点下属的子树的颜色的种类。注意只有至多60种颜色。
题解:显然可以按树的dfs序建立线段树。每次对v节点改颜色,就找到v节点子树的范围,进行60次区间改值。然后对于v节点查颜色,就进行60次区间查询。但是感觉复杂度很爆炸。原来这题的idea是用64位整数存这个区间的信息。这样就节约了60次重复的操作。
struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 400000;
ll st[(MAXN << 2) + 5];
ll lazy[(MAXN << 2) + 5];
void PushUp(int o) {
st[o] = st[ls] | st[rs];
}
void PushDown(int o, int l, int r) {
if(lazy[o]) {
lazy[ls] = lazy[o];
lazy[rs] = lazy[o];
int m = l + r >> 1;
st[ls] = lazy[o];
st[rs] = lazy[o];
lazy[o] = 0;
}
}
void Build(int o, int l, int r) {
if(l == r)
st[o] = 0;
else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
lazy[o] = 0;
}
void Update(int o, int l, int r, int ql, int qr, ll v) {
if(ql <= l && r <= qr) {
lazy[o] = v;
st[o] = v;
return;
} else {
PushDown(o, l, r);
int m = l + r >> 1;
if(ql <= m)
Update(ls, l, m, ql, qr, v);
if(qr >= m + 1)
Update(rs, m + 1, r, ql, qr, v);
PushUp(o);
}
}
ll Query(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return st[o];
} else {
PushDown(o, l, r);
int m = l + r >> 1;
ll res = 0;
if(ql <= m)
res = Query(ls, l, m, ql, qr);
if(qr >= m + 1)
res = res | Query(rs, m + 1, r, ql, qr);
return res;
}
}
#undef ls
#undef rs
} st;
int n, q;
int v[400005];
vector<int> G[400005];
int L[400005], R[400005], dtop;
void dfs(int u, int p) {
L[u] = ++dtop;
for(auto &v : G[u]) {
if(v == p)
continue;
dfs(v, u);
}
R[u] = dtop;
}
void test_case() {
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; ++i) {
scanf("%d", &v[i]);
--v[i];
}
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dtop = 0;
dfs(1, 0);
st.Build(1, 1, n);
for(int i = 1; i <= n; ++i)
st.Update(1, 1, n, L[i], L[i], 1ll << v[i]);
while(q--) {
int op, p, c;
scanf("%d%d", &op, &p);
if(op == 1) {
scanf("%d", &c);
--c;
st.Update(1, 1, n, L[p], R[p], 1ll << c);
} else {
ll res = st.Query(1, 1, n, L[p], R[p]);
int cnt = 0;
while(res) {
cnt += (res & 1);
res >>= 1;
}
printf("%d\n", cnt);
}
}
}
提示:
1、dfs序,可以在进入u的时候,把区间左端点标记为u的dfn序,然后遍历其子树,当从其子树返回时,当前的dfn序就是右端点。也就是:
int L[400005], R[400005], dtop;
void dfs(int u, int p) {
L[u] = ++dtop;
for(auto &v : G[u]) {
if(v == p)
continue;
dfs(v, u);
}
R[u] = dtop;
}
2、只需要知道某颜色是否存在,这个用一个bit就可以表示。
*F - Xors on Segments
这个数据量看起来很像莫队算法。
题意:定义一种函数 \(f(l,r)=l \oplus l+1 \oplus ... \oplus r\) ,这个函数仅当 \(l \leq r\) 时有定义。给一个 \(n(n\leq 5 \cdot 10^4)\)数组 \(a\) , \(a_i \leq 10^6\) ,询问 \(m(\leq 5 \cdot 10^3)\) 次,每次询问一对 \(x,y\) ,求出 $\max\limits_{i=x,j=x}^{i \leq y,j \leq y} f(a_i,a_j) (a_i \leq a_j) $