从这里开始
瞎扯
打比赛,发现自己特别菜。
居然还苟且水上紫名
这个号不敢玩了。要努力学习才能对得起紫名啊,不然再玩两场就灰了。
然后日常瞎扯一下比赛状况。比如奇葩的AC顺序:A->D->B->C。
为啥B题分辣么低?Wrong Answer -(发现自己犯傻)-> Pretests Passed -(发现严重bug)-> Resubmission
C居然数组开小1倍RE了一次,白白丢50分。
由于做出B的时间有点晚,Hack的机会被Room里一个1小时做不动题的Master全抢走了(好吧,其实是有些人代码看不懂)
最开始看完B没有一眼切,后来AC的时候还想复杂了qwq。
只能切完D稳定情绪再回来做。
然后吐槽一下比赛吧。
System Test 前 rk 300+,System Test 后 rk 200+。
为啥?看一下Room里的情况:
fst + hack专场?
(mcfx差点rk 1)。
F好像是bzoj某道合宿题的简化版(看完题解觉得好水。。怪不得OwenOwl一直说这次F好水,好后悔没来打。dream_maker同学说他会$O(n^3)$,不会$O(n^2\log n)$,我很想喷一句。。),E可以用神奇的构造过掉。G?修说是神仙题。
(最近可能有点忙,坑填得会很慢)
-->
Problem A Doggo Recoloring
题目大意
给定一个只包含小写字母的字符串,每次可以将一种至少出现了2次的字符都变成另一个字符。问是否可能使所有字符一样。
特判$n = 1$的时候。其他时候看有没有一个字符出现了两次。
Code
/**
* Codeforces
* Problem#1025D
* Accepted
* Time: 31ms
* Memory: 100k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 1e5 + ; int n;
char str[N];
boolean aflag = false;
boolean vis[]; int main() {
scanf("%d", &n);
scanf("%s", str);
if (n == ) {
puts("YES");
return ;
}
for (int i = ; i < n; i++)
if (vis[str[i] - 'a']) {
puts("YES");
return ;
} else
vis[str[i] - 'a'] = true;
puts("NO");
return ;
}
Problem A
Problem B Weakened Common Divisor
题目大意
给定$n$个二元组$(a_{i}, b_{i})$,问是否存在一个$d > 1$,使得对于每个$1\leqslant i \leqslant n$满足$d \mid a_{i}$或者$d \mid b_{i}$。
首先讲一个沙雕做法。
暴力枚举$a_{1} \times b_{1}$的质因子。然后$O(n)$检查。时间复杂度$O(\sqrt{V} + n\log{V})$。
然后讲考场上我的zz做法。
如果$d$满足条件,那么$d$能够整除$lcm(a_{i}, b_{i})$。就是说$d$能整除每一对的最小公倍数的最大公约数$g$。
然后找它较小的约数就是答案(因为上面那句话只是必要条件,但不是充分条件,比如可能$d > a_{i}$且$d > b_{i}$,但$d\mid a_{i}b_{i}$)。(少了这一步 WA * 1)
找这个合法的约数不能暴力找(然后暴力找能过pretest,后来发现resubmission * 1)
然后发现每个数有用的部分其实是它和这个$g$的最大公约数,取这中间非1的最小值就行了。
Code
/**
* Codeforces
* Problem#1025B
* Accepted
* Time: 139ms
* Memory: 2400k
*/
#include<bits/stdc++.h>
#define ll long long
using namespace std; const int N = ; int n;
int a[N], b[N];
ll ls[N]; ll gcd(ll a, ll b) {
return (!b) ? (a) : (gcd(b, a % b));
} int main(){
scanf("%d", &n); for (int i = ; i <= n; i++)
scanf("%d%d", a + i, b + i); for (int i = ; i <= n; i++)
ls[i] = a[i] / gcd(a[i], b[i]) * b[i];
ll res = ls[]; for (int i = ; i <= n; i++)
res = gcd(res, ls[i]);
for (int i = ; i <= n; i++) {
ll x = gcd(res, a[i]);
if (x > )
res = min(res, x);
ll y = gcd(res, b[i]);
if (y > )
res = min(res, y);
} if (res == )
cout << -;
else
cout << res;
return ;
}
Problem B
Problem C Plasticine zebra
题目大意
给定一个只含 'b','w' 的串,每操作可以选择两个字符间的一个位置,然后将两边翻转。问可以得到的最长的连续的交错的串的长度。
来手玩一下这个操作。
$ab|c \rightarrow b^{T}|a^{T}c^{T}\rightarrow bca$。
然后发现任意两次连续的操作会使得某一段被移动到字符串的最右端。
如果操作只进行奇数次?比如:$ab|cd \rightarrow b^{T}a^{T}d^{T}c^{T}$,其中$a^{T}d^{T}$是最优的答案。
所以$a$的第一个字符和$d$的最后一个字符不同,我们可以将$abcd \rightarrow bcda$,这样可以得到同样答案(至是串的顺序反了一下)。
因此可以将原串复制一份粘在后面,然后暴力跑答案。答案记得和$n$取最小值。
Code
/**
* Codeforces
* Problem#1025
* Accepted
* Time: 31ms
* Memory: 300k
*/
#include<bits/stdc++.h>
using namespace std; const int N = 1e5 + ; char str[N << ];
int main(){
scanf("%s", str + );
int n = strlen(str + ), res = , cnt = ;
for (int i = ; i <= n; i++)
str[i + n] = str[i];
str[ * n] = str[ * n - ];
for (int i = ; i < * n; i++)
if (str[i] != str[i + ])
cnt++;
else
res = max(res, cnt), cnt = ;
printf("%d\n", min(res, n));
return ;
}
Problem C
Problem D Recovering BST
题目大意
给定一个严格单调递增的数组,问能否构成一个二叉搜索树使得一条边的两端点的键值的最大公约数大于1。
noip普及组难度?
考虑区间动态规划。用$f[i][j]$表示区间$[l, r]$内的数能否构成一棵二叉树。
不。这是有问题的。我们还需要涉及它的父节点。但是它要么是$l - 1$,要么是$r + 1$,因此只需再增加一个$[0 / 1]$就行了。
每次转移枚举当前区间内的根。
由于残暴的$O(n^3)$数据范围有点可怕。不敢多带个$log$。预处理任意两个数的$gcd$。
Code
/**
* Codeforces
* Problem#1025D
* Accepted
* Time: 93ms
* Memory: 3900k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = ; int gcd(int a, int b) {
return (!b) ? (a) : (gcd(b, a % b));
} int n;
int ar[N];
int gs[N][N];
boolean vis[N][N][];
boolean f[N][N][]; inline void init() {
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%d", ar + i);
for (int i = ; i <= n; i++)
for (int j = ; j <= n; j++)
gs[i][j] = gcd(ar[i], ar[j]);
} boolean dp(int l, int r, int dir, int rt) {
if (l > r)
return true;
if (vis[l][r][dir])
return f[l][r][dir];
vis[l][r][dir] = true;
for (int i = l; i <= r && !f[l][r][dir]; i++)
if (!rt || gs[rt][i] > )
f[l][r][dir] |= dp(l, i - , , i) && dp(i + , r, , i);
return f[l][r][dir];
} inline void solve() {
if (dp(, n, , ))
puts("Yes");
else
puts("No");
} int main() {
init();
solve();
return ;
}
Problem D
Problem E Colored Cubes
题目大意
给定一个$n\times n$的网格图,图上有$m$个两两位置不同的箱子,它们各有一个目标位置,每次可以将一个箱子移动它相邻的一个空格子(不能移出边界)。要求在少于10800步内将所有箱子移动到它对应的位置。
开始一直以为是一道图论题。原来是构造题。下来想想如果要水过去,感觉不是很难。
$m\leqslant n$是个优秀的性质。主对角线有着优美的性质(比如每个箱子可以向四个方向移动)。
考虑把所有箱子先移动到主对角线上。显然这样是可行的。对于主对角线上的每个空位置你一定可以找到一个不在主对角线上的箱子能够移动到它(如果还有箱子不在主对角线上,从它开始bfs就能找到)。
然后对主对角线上的箱子按它们对应的目标位置进行排序,第一关键字是横坐标,第二关键字是纵坐标。交换两个主对角线上的箱子可以通过将一个先移开,另一个移到它原来的位置上,再把它移动过去完成交换。用选择排序 / 置换 / 冒泡排序可以做到移动总次数$O(n^{2})$。
考虑把每个箱子移到它目标的那一行上,按照横坐标的顺序从左边开始排列。简单说明一下一定可行。因为每一个箱子所在的那一列是一定通畅的。
最后一步就特别简单了。每一行从后向前把个箱字移动到它所在的位置上。
因为我比较菜,移动总次数$O(n^{2})$但常数有点大,第一步移动需要贪一下心才能满足10800步的限制。
标算大概是先移动箱子使得$x$坐标互不相同,然后将所有箱子移到对角线上,然后用同样的方法将目标位置的箱子移动到对角线上,再将这些操作反转。它能保证移动总次数不超过$4\times n^{2}$。
Code
/**
* Codeforces
* Problem#1025E
* Accepted
* Time: 78ms
* Memory: 600k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define ppp pair<Point, Point>
#define fi first
#define sc second const int N = ; typedef class Point {
public:
int x, y; Point (int x = , int y = ):x(x), y(y) {} boolean operator < (Point b) const {
if (x ^ b.x) return x < b.x;
return y < b.y;
}
}Point; void readPoint(Point& p) {
scanf("%d%d", &p.x, &p.y);
} #define pv(_array, _pos) ((_array)[_pos.x][_pos.y]) int n, m;
boolean used[N][N], sec[N];
Point ps[N], pt[N];
pair<Point, int> buf[N], bs[N];
int place[N], rk[N], put[N]; inline void init() {
scanf("%d%d", &n, &m);
for (int i = ; i <= m; i++) {
readPoint(ps[i]);
bs[i] = make_pair(ps[i], i);
if (ps[i].x == ps[i].y && ps[i].x <= m)
place[ps[i].x] = i, sec[i] = true;
pv(used, ps[i]) = true;
}
for (int i = ; i <= m; i++) {
readPoint(pt[i]);
buf[i] = make_pair(pt[i], i);
}
} const int mov[][] = {{, }, {, }, {-, }, {, -}}; boolean inq[N][N];
Point ls[N][N];
queue<Point> que;
boolean arrivable(Point s, Point t) {
memset(inq, false, sizeof(inq));
inq[s.x][s.y] = true;
que.push(s);
while (!que.empty()) {
Point e = que.front();
que.pop();
for (int k = ; k < ; k++) {
Point eu (e.x + mov[k][], e.y + mov[k][]);
if (eu.x < || eu.x > n || eu.y < || eu.y > n)
continue;
if (used[eu.x][eu.y] || inq[eu.x][eu.y])
continue;
ls[eu.x][eu.y] = e;
inq[eu.x][eu.y] = true;
que.push(eu);
}
}
return inq[t.x][t.y];
} vector< pair<Point, Point> > path;
void printPath(Point cur, Point s) {
if (cur.x == s.x && cur.y == s.y)
return;
Point nx = ls[cur.x][cur.y];
printPath(nx, s);
path.push_back(ppp(nx, cur));
} boolean move(Point start, Point end) {
if (!arrivable(start, end))
return false;
printPath(end, start);
pv(used, start) = false;
pv(used, end) = true;
return true;
} void swapPoint(int a, int b) {
move(Point(a, a), Point(a, b));
move(Point(b, b), Point(a, a));
move(Point(a, b), Point(b, b));
swap(place[a], place[b]);
} int cnt[N];
boolean finished[N]; inline void solve() {
sort(bs + , bs + m + );
sort(buf + , buf + m + );
for (int i = ; i <= m; i++)
rk[buf[i].second] = i; for (int i = ; i <= m; i++) {
if (place[i])
continue;
boolean aflag = false;
for (int k = ; k <= m && !aflag; k++) {
int j = bs[k].second;
if (!sec[j] && move(ps[j], Point(i, i))) {
ps[j] = Point(i, i);
place[i] = j, sec[j] = true;
}
}
} // for (int i = 1; i <= m; i++)
// cerr << place[i] << " ";
// cerr << "==============" << endl; for (int i = ; i <= m; i++) {
int pos = ;
for (int j = i; j <= m; j++)
if (rk[place[j]] == i) {
pos = j;
break;
}
if (pos ^ i)
swapPoint(i, pos);
// cerr << i << " " << place[i] << endl;
} for (int i = ; i <= m; i++) {
int x = buf[i].first.x;
move(Point(i, i), Point(x, ++cnt[x]));
ps[buf[i].second] = Point(x, cnt[x]);
} for (int i = m; i; i--)
move(ps[buf[i].second], buf[i].first); printf("%d\n", (signed) path.size());
for (int i = ; i < (signed) path.size(); i++)
printf("%d %d %d %d\n", path[i].first.x, path[i].first.y, path[i].second.x, path[i].second.y);
} int main() {
srand(233u);
init();
solve();
return ;
}
Problem E
Problem F Disjoint Triangles
题目大意
给定平面上$n$个不同点,问存在多少对以给定点为顶点的两个三角形使得它们没有公共部分。
感觉没法补集转化之类的。考虑选取恰当的计数点。
考虑经过两个三角形的两个端点的直线,且能将两个三角形划分到两个平面内。
对于任意一对合法的三角形恰好存在2条这样的直线(画画图可以发现,我也不会证明qwq)。
每条这样的切线有2种选法(每个点都可以和左侧的点构成三角形也可以和右侧)。可以规定它只和左侧的点构成三角形(枚举直线的时候要定向)。
这样可以$O(n^{3})$解决掉这个问题。
考虑枚举一个点,每次乱枚举另一个点的时候很浪费。直接排极角序,然后就可以线性扫一圈了。
时间复杂度$O(n^{2}\log n)$
Code
/**
* Codeforces
* Problem#1025F
* Accepted
* Time: 607ms
* Memory: 100k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;
#define ll long long const double eps = 1e-; typedef class Point {
public:
double x, y; Point(double x = 0.0, double y = 0.0):x(x), y(y) { }
}Point, Vector; Point operator - (Point a, Point b) {
return Point(a.x - b.x, a.y - b.y);
} double cross(Vector u, Vector v) {
return u.x * v.y - u.y * v.x;
} int n;
Point* ps; inline void init() {
scanf("%d", &n);
ps = new Point[(n + )];
for (int i = ; i <= n; i++)
scanf("%lf%lf", &ps[i].x, &ps[i].y);
} int cnt = ;
ll res = ;
pair<double, int> *ar;
boolean *vis; int getNext(int p) {
if (p == cnt)
return ;
return p + ;
} inline void solve() {
vis = new boolean[(n + )];
ar = new pair<double, int> [(n + )];
for (int i = ; i <= n; i++) {
cnt = ;
for (int j = ; j <= n; j++)
if (j ^ i) {
Vector dif = ps[j] - ps[i];
ar[++cnt] = make_pair(atan2(dif.y, dif.x), j);
}
sort(ar + , ar + cnt + );
int p = , cntl = ;
memset(vis, false, sizeof(boolean) * (n + ));
for (int j = ; j < n; j++) {
Vector dif = ps[ar[j].second] - ps[i];
for (int np = getNext(p); np != j && cross(dif, ps[ar[np].second] - ps[i]) > ; p = np, np = getNext(np))
cntl += !vis[np], vis[np] = true;
res = res + cntl * 1ll * (cntl - ) / * (n - cntl - ) * 1ll * (n - cntl - ) / ;
// cntl--;
int np = ar[getNext(j)].second, nj = getNext(j);
if (cross(dif, ps[np] - ps[i]) > )
cntl--, vis[nj] = false;
else {
if (p == j)
p = getNext(j);
}
}
// cerr << i << " " << res << endl;
} cout << (res >> ) << endl;
} int main() {
init();
solve();
return ;
}
Problem F
Problem G Company Acquisitions
题目大意
有$n$个公司,每个公司要么是独立的,要么被一个独立的公司收购。
如果存在不止1个公司是独立的,每天那么会随机选择两个独立的公司$A,B$,然后有二分之一的概率$A$被$B$收购,二分之一的概率$B$被$A$收购。如果$A$被$B$收购,那么$A$所有收购的公司会变成独立的。
给定初始局面,问只存在1个独立的公司期望所需要的天数。
定义一个收购了$k$个公司$A$的势能为$2^{k} - 1$。定义一个局面的势能为所有公司的势能的和。
考虑选择了两个分别收购了$p,q$个公司的公司后势能变化量的期望:$\frac{1}{2}\left[2^{p + 1}- 1 - (2^{p} - 1 + 2^{q} - 1)\right] + \frac{1}{2}\left[2^{q + 1}-1-(2^{p} - 1 + 2^{q} - 1) \right ] = 1$。
所以$E\left ( \Delta \Phi \right )=1$。
我们知道目标局面的势能是$2^{n - 1} - 1$。然后可以计算期望的步数了。
(其实是考虑硬点每个状态的期望要的步数,然后可以发现可以满足一堆方程)
Code
/**
* Codeforces
* Problem#1025G
* Accepted
* Time: 31ms
* Memory: 300k
*/
#include <iostream>
#include <cstdio>
using namespace std;
typedef bool boolean; const int N = , M = 1e9 + ; int sub(int a, int b) {
a -= b;
if (a < )
return a + M;
return a;
} int qpow(int a, int p) {
int rt = , pa = a;
for ( ; p; p >>= , pa = pa * 1ll * pa % M)
if (p & )
rt = rt * 1ll * pa % M;
return rt;
} int n;
inline void init() {
scanf("%d", &n);
} int res;
int follower[N];
inline void solve() {
res = sub(qpow(, n - ), );
for (int i = , x; i <= n; i++) {
scanf("%d", &x);
if (x != -)
follower[x]++;
}
for (int i = ; i <= n; i++)
res = sub(res, sub(qpow(, follower[i]), ));
printf("%d\n", res);
} int main() {
init();
solve();
return ;
}
Problem G