Codeforces Round #609 (Div. 2)
准备掉回去div2了,每次都掉不回去,按照我之前立的flag:全部及格就变灰,现在已经及格了1科了,估计我的cf账号准备不保了。
这么菜还是把div2的AB也写了吧,上次div2only就是卡B,真的演,快把自己演到爆零了。(好像有人专门看div2的AB的题解是咋回事)
A - Equation
题意:给一个n<=1e7,找两个合数a和b使得a-b的差为n。
题解:???这啥东西,真就送分?
构造a=3n,b=2n,必含有公因子n,只有当n是1的时候是特例。
void test_case() {
int n;
scanf("%d", &n);
if(n == 1) {
puts("9 8");
return;
}
printf("%d %d\n", 3 * n, 2 * n);
}
B - Modulo Equality
题意:给一个序列a,一个序列b,把这两个序列任意排序,然后使得对应位置的差在模意义下相等。
题解:数据量比较小,全部排序然后枚举错开的位置就可以。
int a[2005], b[4005];
void test_case() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++i)
scanf("%d", &b[i]);
sort(b + 1, b + 1 + n);
for(int i = 1; i <= n; ++i)
b[i + n] = b[i];
int ans = INF;
for(int d = 0; d < n; ++d) {
int dif = (b[1 + d] - a[1] + m) % m;
for(int i = 1; i <= n; ++i) {
if((b[i + d] - a[i] + m) % m != dif) {
dif = INF;
break;
}
}
ans = min(ans, dif);
}
printf("%d\n", ans);
}
好像有人说要去找很多其他人的博客才能看懂,太有毅力了,想起我大一的时候都是看一下不会就关掉的。
那说一下可能详细一点的思路?
假如不是模运算下的减法,就直接排序之后对应位置作差,然后看差是不是都相等。
但是这里是模意义下的减法,存在小数字应该和大数字对应的情况,但是由于ai和bi的取值范围是[0,m),这种情况只能出现一次,那么把b[i+n]复制一遍就可以。
意思就是bi本身比如是:
0 0 1 2 3 3 4
这里是模5意义下,那么这里bi复制一遍,相当于:
0 0 1 2 3 3 4 | 0 0 1 2 3 3 4
0 0 1 2 3 3 4 | 5 5 6 7 8 8 9
算了好像这东西还是像数学,都是看直觉和缘分。
C - Long Beautiful Integer
题意:给一个n位10进制数字串s(首位不为0),构造一个数字串t(首位不为0),使得t串是有周期k,且t串>=s串,且t串最小。
题解:想了一大堆乱七八糟的做法,最后其实很简单。把数字s的前k位复制若干次直到长度为n,然后和s比较,假如比s小就给这个数字s的前k位代表的数字+1,然后再复制若干次,这次一定更大。
这个构造是最小的,因为要比s大,所以前k位都是至少要和s相同,然后受限于周期性后面只能够不断重复。假如最后弄出来的不够大那么在前面的数字位+1之后就已经从第k位开始严格比s大了。小心连续进位的情况:
6 3
299387
但是不可能会进位得到比n位还长的数字,因为没有串可以比999..9更大。
吸取教训以后要写直接测试多组的程序,毕竟我的电脑编译运行是真的慢。
6 2
163456
7 2
1934569
7 2
1917165
int n, k;
char s[200005];
char t[200005];
void test_case() {
scanf("%d%d%s", &n, &k, s + 1);
for(int i = 1; i <= k; ++i) {
t[i] = s[i];
for(int j = i; j <= n; j += k)
t[j] = t[i];
}
bool suc = 1;
for(int i = 1; i <= n; ++i) {
if(t[i] < s[i])
suc = 0;
else if(t[i] > s[i])
break;
}
if(suc) {
printf("%d\n", n);
puts(t + 1);
return;
}
for(int i = k; i >= 1; --i) {
++t[i];
if(t[i] <= '9') {
for(int j = i; j <= n; j += k)
t[j] = t[i];
break;
} else {
t[i] = '0';
for(int j = i; j <= n; j += k)
t[j] = t[i];
}
}
printf("%d\n", n);
puts(t + 1);
}
D - Domino for Young
下次再看到这种骨牌题就直接染色了。我记得lrj的紫书里面有一个这样的题的,但是我居然不记得可以这样用。其实比赛中我有往这个方向想但是好像没有想到要怎么构造。
首先黑白染色之后答案不可能超过黑白格子之中的最小值,这个是显然的,然后官方题解教了怎么构造一种可以达到这个最小值的办法。首先把两行相邻的相等的和两列相邻的相等的全部消掉,然后就会得到一种“基本图案”,这个基本图案长得像楼梯不过并不是每层都是45度的,但是没有任何相邻的两列是同高的了,是严格单调减的,也没有两行完全相同。由于上面是两行两列一起消掉的所以不会改变原图的颜色,而很显然的对这个基本图案的改变可以对应的原图的改变(好难形容啊)。然后把左下角染成黑色,指定每个黑色优先与上方的白色配对,假如上方没有则尝试和左方的配对,这样好像就刚刚好配对完了。
所以答案就是黑白格子数量的最小值。
void test_case() {
int n;
scanf("%d", &n);
ll cnt0 = 0, cnt1 = 0;
for(int i = 1, ai; i <= n; ++i) {
scanf("%d", &ai);
if(i & 1) {
cnt0 += (ai + 1) / 2;
cnt1 += ai / 2;
} else {
cnt1 += (ai + 1) / 2;
cnt0 += ai / 2;
}
}
printf("%lld\n", min(cnt0, cnt1));
}
总结:下次看见这种棋盘然后又是骨牌、行列交叉之类的都应该往黑白染色的方向试一下。
E - K Integers
题意:给一个n排列,对每个k属于[1,n],求使得[1,k]的连续升序的子串出现在序列中所需要的最小的临位交换次数f(k)
题解:首先f(1)=0,其次f(2)=|dis(1)-dis(2)|-1+cost,cost就是这个1和2顺序有没有反过来。但是这个f(3)怎么搞呢?想到一个解法,会不会是要让他们几个先全部连在一起,就是把他们之间的元素全部换出去,然后再排他们自己的顺序呢?他们的顺序是应该怎么排呢?
1 2 3 -> 0
1 3 2 -> 1
2 1 3 -> 1
2 3 1 -> 2
3 1 2 -> 2
3 2 1 -> 3
?这个是逆序数的意思吗?模拟比赛的状态的话按我的性格就去写一发。
偷偷看了一下数据,好像是把无关数字搬到外面的成本算错了,并不是每个数字搬出去都只需要1单位的成本。想了一下好像无关数字的成本取决于它左侧和右侧的当前数字的数量的最小值,不过这个怎么维护呢?好像也是可以用线段树批量给一个区间+1-1的。但是这好像很难维护(好像根本就是不能维护)。好像不得不统计这一段区间中位于中间的点的位置然后分几种情况处理。
真的好复杂耶,不过至少证明直觉是对的,而且码力up了些微。
struct TreapNode {
int val1, val2;
TreapNode() {}
TreapNode(int val1, int val2): val1(val1), val2(val2) {}
bool operator<(const TreapNode& tn)const {
return val1 < tn.val1;
}
bool operator<=(const TreapNode& tn)const {
return val1 <= tn.val1;
}
bool operator==(const TreapNode& tn)const {
return val1 == tn.val1;
}
bool operator>=(const TreapNode& tn)const {
return val1 >= tn.val1;
}
bool operator>(const TreapNode& tn)const {
return val1 > tn.val1;
}
} TNINF(INF, INF);
struct Treap {
#define ls ch[id][0]
#define rs ch[id][1]
static const int MAXN = 200000;
int ch[MAXN + 5][2], dat[MAXN + 5];
TreapNode val[MAXN + 5];
int cnt[MAXN + 5];
int siz[MAXN + 5];
int tot, root;
void Init() {
tot = 0;
root = 0;
}
int NewNode(TreapNode v, int num) {
int id = ++tot;
ls = rs = 0;
dat[id] = rand();
val[id] = v;
cnt[id] = num;
siz[id] = num;
return id;
}
void PushUp(int id) {
siz[id] = siz[ls] + siz[rs] + cnt[id];
}
void Rotate(int &id, int d) {
int temp = ch[id][d ^ 1];
ch[id][d ^ 1] = ch[temp][d];
ch[temp][d] = id;
id = temp;
PushUp(ch[id][d]);
PushUp(id);
}
//插入num个v
void _Insert(int &id, TreapNode v, int num) {
if(!id)
id = NewNode(v, num);
else {
if(v == val[id])
cnt[id] += num;
else {
int d = val[id] > v ? 0 : 1;
_Insert(ch[id][d], v, num);
if(dat[id] < dat[ch[id][d]])
Rotate(id, d ^ 1);
}
PushUp(id);
}
}
//删除至多num个v
void _Remove(int &id, TreapNode v, int num) {
if(!id)
return;
else {
if(v == val[id]) {
if(cnt[id] > num) {
cnt[id] -= num;
PushUp(id);
} else if(ls || rs) {
if(!rs || dat[ls] > dat[rs])
Rotate(id, 1), _Remove(rs, v, num);
else
Rotate(id, 0), _Remove(ls, v, num);
PushUp(id);
} else
id = 0;
} else {
val[id] > v ? _Remove(ls, v, num) : _Remove(rs, v, num);
PushUp(id);
}
}
}
//查询v的排名,排名定义为<v的数的个数+1。
int _GetRank(int id, TreapNode v) {
int res = 1;
while(id) {
if(val[id] > v)
id = ls;
else if(val[id] == v) {
res += siz[ls];
break;
} else {
res += siz[ls] + cnt[id];
id = rs;
}
}
return res;
}
//查询排名为rk的数,rk必须是正整数,rk过大返回无穷
TreapNode _GetValue(int id, int rk) {
TreapNode res = TNINF;
while(id) {
if(siz[ls] >= rk)
id = ls;
else if(siz[ls] + cnt[id] >= rk) {
res = val[id];
break;
} else {
rk -= siz[ls] + cnt[id];
id = rs;
}
}
return res;
}
int Size() {
return siz[root];
}
void Insert(TreapNode v, int num = 1) {
_Insert(root, v, num);
}
void Remove(TreapNode v, int num = INF) {
_Remove(root, v, num);
}
int GetRank(TreapNode v) {
return _GetRank(root, v);
}
TreapNode GetValue(int rk) {
return _GetValue(root, rk);
}
#undef ls
#undef rs
} treap1, treap2;
int n;
int a[200005], p[200005];
ll ans[200005];
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
p[a[i]] = i;
}
treap1.Init();
treap2.Init();
int Lmost = p[1], Rmost = p[1];
ll cnt1 = 0, cnt2 = 0;
TreapNode cur(p[1], 1);
treap1.Insert(cur);
treap2.Insert(cur);
TreapNode mid1, mid2;
mid1 = mid2 = cur;
ans[1] = 0;
for(int i = 2; i <= n; ++i) {
cur = TreapNode(p[i], i);
treap1.Insert(cur, 1);
mid1 = treap1.GetValue((i + 1) / 2);
mid2 = treap1.GetValue(i / 2 + 1);
if(p[i] < Lmost) {
for(int j = Lmost - 1; j >= p[i]; --j)
treap2.Insert(TreapNode(j, a[j]));
Lmost = p[i];
} else if(p[i] > Rmost) {
for(int j = Rmost + 1; j <= p[i]; ++j)
treap2.Insert(TreapNode(j, a[j]));
Rmost = p[i];
}
cnt1 += i - treap1.GetRank(cur);
if(p[i] >= mid2.val1) {
cnt2 -= treap2.Size() - treap2.GetRank(cur);
cnt2 += treap2.Size() - treap2.GetRank(mid1) - i / 2;
} else if(p[i] <= mid1.val1) {
cnt2 -= treap2.GetRank(cur);
cnt2 += treap2.GetRank(mid2) - i / 2;
}
ans[i] = cnt1 + cnt2;
}
for(int i = 1; i <= n; ++i)
printf("%lld%c", ans[i], " \n"[i == n]);
}
也可以把这个struct给define成自带数据类型。
看了一下神仙的做法可以直接用两个set来做对顶堆的。这样就不用考虑这么多左边右边的情况了。但是更绝的来了,直接用pb_ds的?的确可能平板电视在处理这种不需要维护名次求和,只需要求名次的基本功能的时候非常方便。
一些减少常数的思路:发现其实上面的解法中第二个关键字根本就没用。treap1求动态中位数可以用两个priority_queue来代替?(这样复杂度就不对了,要用普通的set),而treap2只是最简单的名次功能,可以用线段树甚至树状数组来代替。
struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
int st[(MAXN << 2) + 5];
void PushUp(int o) {
st[o] = st[ls] + st[rs];
}
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);
}
}
void Update(int o, int l, int r, int p, int v) {
if(l == r) {
st[o] += v;
return;
} else {
int m = l + r >> 1;
if(p <= m)
Update(ls, l, m, p, v);
if(p >= m + 1)
Update(rs, m + 1, r, p, v);
PushUp(o);
}
}
int Query(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return st[o];
} else {
int m = l + r >> 1;
int res = 0;
if(ql <= m)
res = 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
} st1, st2;
set<int> LL, RR;
int n;
int a[200005], p[200005];
ll ans[200005];
void test_case() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
p[a[i]] = i;
}
st1.Build(1, 1, n);
st2.Build(1, 1, n);
LL.clear();
RR.clear();
st1.Update(1, 1, n, p[1], 1);
st2.Update(1, 1, n, p[1], 1);
LL.insert(p[1]);
int Lmost = p[1], Rmost = p[1];
ll cnt1 = 0, cnt2 = 0;
ans[1] = 0;
for(int i = 2; i <= n; ++i) {
cnt1 += st1.Query(1, 1, n, p[i], n);
st1.Update(1, 1, n, p[i], 1);
if(p[i] < Lmost) {
for(int j = Lmost - 1; j >= p[i]; --j)
st2.Update(1, 1, n, j, 1);
Lmost = p[i];
} else if(p[i] > Rmost) {
for(int j = Rmost + 1; j <= p[i]; ++j)
st2.Update(1, 1, n, j, 1);
Rmost = p[i];
}
if(!RR.empty() && p[i] > *RR.begin()) {
RR.insert(p[i]);
while(RR.size() > LL.size()) {
LL.insert(*RR.begin());
RR.erase(*RR.begin());
}
} else {
LL.insert(p[i]);
while(LL.size() > RR.size() + 1) {
RR.insert(*LL.rbegin());
LL.erase(*LL.rbegin());
}
}
int mid1, mid2;
mid1 = *LL.rbegin();
mid2 = (LL.size() == RR.size()) ? (*RR.begin()) : (mid1);
if(p[i] >= mid2) {
int ql = mid1 + 1, qr = p[i];
cnt2 += (ql <= qr ? st2.Query(1, 1, n, ql, qr) : 0) - i / 2;
} else if(p[i] <= mid1) {
int ql = p[i] + 1, qr = mid2;
cnt2 += (ql <= qr ? st2.Query(1, 1, n, ql, qr) : 0) - i / 2;
}
ans[i] = cnt1 + cnt2;
}
for(int i = 1; i <= n; ++i)
printf("%lld%c", ans[i], " \n"[i == n]);
}
注意区分end()和rbebin(),以及判断右set为空的时候应该插进左边set里。