A - Function Height
题意: 在平面直角坐标系中有\(2n+1\)个点,分别是\(P_i=(i,0),0\le i\le 2n\)。对于每个奇数\(i\),你一个操作可以将\(P_i\)向上移动\(1\)个单位。要让你用若干次操作使得操作后所有三角形的面积为\(k\)。问你操作后的\(\min\limits_{1\le i\le n}Y_{P_i}\),其中\(Y_p\)表示点\(p\)的纵坐标。
显然要让所有点的横坐标尽量平均。又因为\(P_i\)的纵坐标每增加\(1\),总面积就增加\(1\)。所以总面积就等于所有增加的高度。所以答案应为$ \left\lceil\dfrac{k}{n}\right\rceil$
代码:
// Code by H~$~C
#include <bits/stdc++.h>
using namespace std;
int main() {
long long n, k;
cin >> n >> k;
cout << (k + n - 1) / n << endl;
return 0;
}
B - Diagonal Walking v.2
题意: 在平面直角坐标系中,定义从\((x,y)\)走一步可以走到\((x-1,y-1),(x-1,y),(x-1,y+1),(x,y-1),(x,y+1),(x+1,y-1),(x+1,y),(x+1,y+1)\),即\(8\)联通。若从\((x,y)\)走一步走到\((x',y')\),且\(|x-x'|=|y-y'|=1\),则称这一步为"好的"。求从\((0,0)\)走到\((n,m)\)用\(k\)步,可以经过一个点多次,最多有多少步是"好的"。如果\(k\)步之内走不到,输出-1
。
分类讨论题。下面默认\(n<m\)
若\(k<m\),则肯定就走不到了;
否则,贪心考虑,先走\(n\)步"好的",然后到达\((n,n)\),剩下的距离为\(m-n\)。接着还可以走\(m-n\)步"好的",到达\((n,m+1)\)或\((n,m)\)。若直接到达了\((n,m)\),且当剩下还要走到步数\(k'\equiv1\pmod2\)时,只能将最后一步"好的"拆成两步。若到达了\((n,m+1)\),那么可以在最后一步之前一直围着\((n,m)\)绕弯子,只有最后一步不是"好的"。
代码:
// Code by H~$~C
#include <bits/stdc++.h>
using namespace std;
long long n, m, k;
void solve() {
cin >> n >> m >> k;
if (n > m) swap(n, m);
if (k < m) {
cout << -1 << endl;
return ;
}
if ((m - n) % 2 == 0 && (k - n) % 2 == 1)
--n, --m, k -= 2;
cout << k - (m - n) % 2 << endl;
}
int main() {
int tests;
cin >> tests;
while (tests--) solve();
return 0;
}
C - Classy Numbers
题意:定义一个数字是"好数",当且仅当它的十进制表示下有不超过\(3\)个数字\(1\sim 9\)。给定\([l,r],1\le l,r\le 10^{18}\),问有多少个\(x\)使得\(l \le x \le r\),且\(x\)是"好数"。多次查询。
打表。预处理出所有的"好数",这个时间复杂度是\(O((18*9)^3)\)的。然后每一次查询直接在打好的表上二分即可。
代码:
// Code by H~$~C
#include <bits/stdc++.h>
using namespace std;
vector<long long> nums;
long long pw[19];
void init() {
pw[0] = 1LL;
for (int i = 1; i < 19; ++i)
pw[i] = pw[i - 1] * 10LL;
nums.push_back(1e18);
for (int i = 0; i < 18; ++i) {
for (int a1 = 1; a1 <= 9; ++a1) {
nums.push_back(pw[i] * a1);
}
for (int j = i + 1; j < 18; ++j) {
for (int a1 = 1; a1 <= 9; ++a1) {
for (int a2 = 1; a2 <= 9; ++a2) {
nums.push_back(pw[i] * a1 + pw[j] * a2);
}
}
for (int k = j + 1; k < 18; ++k) {
for (int a1 = 1; a1 <= 9; ++a1) {
for (int a2 = 1; a2 <= 9; ++a2) {
for (int a3 = 1; a3 <= 9; ++a3) {
nums.push_back(pw[i] * a1 + pw[j] * a2 + pw[k] * a3);
}
}
}
}
}
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
}
inline int get_num(long long x) {
return upper_bound(nums.begin(), nums.end(), x) - nums.begin();
}
void solve() {
long long l, r;
cin >> l >> r;
cout << get_num(r) - get_num(l - 1) << endl;
}
int main() {
init();
int tests;
cin >> tests;
while (tests--) solve();
return 0;
}
D - Vasya and Arrays
题意:给定\(A,B\)两个数列,长度分别为\(n,m\),数列长度不超过\(3 \times 10^5\),数列中每个元素的值为\([1,10^9]\)的整数。你需要将两个数列都分割成\(k\)份,使得每一份的元素和对应相等。如果可行,输出最多将序列分割成多少份,如果不可行,输出-1
。
如果\(\sum\limits_{i=1}^nA_i\not=\sum\limits_{i=1}^mB_i\),则输出-1
。接着用双指针,若当前数组\(A\)上的区间的和等于数组\(B\)上的区间和,则答案\(+1\),更新两个数组上的左区间。
代码:
// Code by H~$~C
#include <bits/stdc++.h>
using namespace std;
static const int Maxn = 300005;
int n, m;
int a[Maxn], b[Maxn];
long long suma, sumb;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i], suma += a[i];
cin >> m;
for (int i = 1; i <= m; ++i)
cin >> b[i], sumb += b[i];
if (suma != sumb) return puts("-1") & 0;
suma = sumb = 0;
int ans = 0;
for (int i = 1, j = 1; i <= n; ++i) {
suma += a[i];
while (j <= m && sumb < suma) sumb += b[j++];
if (suma == sumb) {
suma = sumb = 0;
++ans;
}
}
cout << ans << endl;
return 0;
}
E - Covered Points
题意:在平面直角坐标系上有\(n\)条线段,求这些线段覆盖的整点的个数。\(1\le n\le 1000\)
可以先把所有所有线段覆盖的整点的和统计一下,然后在减去重复的,即线段的交点出现的次数\(-1\)。
但线段的交点不是特别好求。
设两条线段为\(p_1=(x_1,y_1),p_2=(x_2,y_2)\)和\(p_3=(x_3,y_3),p_4=(x_4,y_4)\)。我们考虑先求这两条线段所在的直线的交点,再判断交点是否在这两条线段上。
若这两条直线平行,即\((p_1-p_2)\times(p_3-p_4)=0\)的话,那么就肯定没有交点。
设交点为\(p_0=(x,y)\),则满足\((p_1-p_0)\times(p_2-p_0)=0,(p_3-p_0)\times(p_4-p_0)=0\)。展开后可得出:
\[(y_1-y_2)x_0+(x_2-x_1)y_0+x_1y_2-x_2y_1=0\]
\[(y_3-y_4)x_0+(x_4-x_3)y_0+x_3y_4-x_4y_3=0\]
然后解这个方程组。
设\(a_1=y_1-y_2,b_1=x_2-x_1,c_1=x_1y_2-x_2y_1,a_2=y_3-y_4,b_2=x_4-x_3,c_2=x_3y_4-x_4y_3\),则最后可求得:
\[x_0=(c_1b_2-c_2b_1)/(a_2b_1-a_1b_2)\]
\[y_0=(a_2c_1-a_1c_2)/(a_1b_2-a_2b_1)\]
至于如何判断点\(p_0\)是否在线段\(p_1,p_2\)上,只需要\((p_0-p_1)\times(p_0-p_2)=0\),且\((p_0-p_1).(p_0-p_2)<0\)即可。
有一个要特别注意的是这题如果你用double
写,会特别卡精度。
代码:
// Code by H~$~C
#include <bits/stdc++.h>
#define x1 x1_19260817
#define y1 y1_19260817
#define fi first
#define se second
using namespace std;
static const double EPS = 1e-8;
inline int dcmp(double x) {
return fabs(x) < EPS ? 0 : (x < 0 ? -1 : 1);
}
static const int Maxn = 1005;
typedef pair<double, double> point;
point operator + (point a, point b) { return point(a.fi + b.fi, a.se + b.se); }
point operator - (point a, point b) { return point(a.fi - b.fi, a.se - b.se); }
inline double dot(point a, point b) { return a.fi * b.fi + a.se * b.se; }
inline double cross(point a, point b) { return a.fi * b.se - a.se * b.fi; }
inline bool is_number(double x) {
bool flag = false;
int p = int(x);
flag |= abs(x - double(p)) <= EPS;
flag |= abs(x - double(p + 1)) <= EPS;
// fprintf(stderr, "is_number(%.12lf) = %d\n", x, flag);
return flag;
}
inline void to_number(double &x) {
int p = int(x);
if (abs(x - double(p)) <= EPS) x = double(p);
else if (abs(x - double(p + 1)) <= EPS) x = double(p + 1);
else if (abs(x - double(p - 1)) <= EPS) x = double(p - 1);
}
inline int tonumber(double x) {
int p = int(x);
if (abs(x - double(p)) <= EPS) return p;
else return p + 1;
}
vector<point> line_intersect(point x1, point y1, point x2, point y2) {
vector<point> res;
if (dcmp(cross(y1 - x1, y2 - x2)) == 0) return res;
point a = point(x1.se - y1.se, x2.se - y2.se);
point b = point(y1.fi - x1.fi, y2.fi - x2.fi);
point c = point(cross(x1, y1), cross(x2, y2));
res.push_back(point(cross(c, b) / cross(b, a), cross(c, a) / cross(a, b)));
}
inline bool point_on_segment(point p, point A, point B) {
if (p == A || p == B) return true;
point a = A - p, b = B - p;
return dcmp(cross(a, b)) == 0 && dcmp(dot(a, b)) <= 0;
}
vector<point> segment_intersect(point x1, point y1, point x2, point y2) {
vector<point> res = line_intersect(x1, y1, x2, y2);
if (res.empty()) return res;
to_number(res[0].fi); to_number(res[0].se);
if (!point_on_segment(res[0], x1, y1) || !point_on_segment(res[0], x2, y2)) res.pop_back();
return res;
}
int n;
long long ans;
int x1[Maxn], x2[Maxn], y1[Maxn], y2[Maxn];
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> x1[i] >> y1[i] >> x2[i] >> y2[i];
}
for (int i = 1; i <= n; ++i) {
ans += __gcd(abs(x1[i] - x2[i]), abs(y1[i] - y2[i])) + 1;
}
for (int i = 1; i <= n; ++i) {
set<pair<int, int>> st;
for (int j = i + 1; j <= n; ++j) {
vector<point> p = segment_intersect(point(x1[i], y1[i]), point(x2[i], y2[i]), point(x1[j], y1[j]), point(x2[j], y2[j]));
if (p.empty()) continue;
point q = p[0];
if (is_number(q.fi) && is_number(q.se)) {
int qx = tonumber(q.fi);
int qy = tonumber(q.se);
if (st.find(make_pair(qx, qy)) == st.end()) {
--ans;
st.insert({qx, qy});
}
}
}
}
printf("%lld\n", ans);
return 0;
}
F - Relatively Prime Powers
题意:对于一个数\(x\),当且仅当将其分解后各个质因数的指数的最大公约数为\(1\)时,我们称这个数是"好的"。多组询问,每次询问\([2,n]\)区间内有多少数是"好的"。\(2\le n\le 10^{18}\)
观察到若数\(x\)不是"好的",则\(x=p^k\),其中\(p,k\)是整数。
所以可以打表,将\(10^{18}\)内所有\(k\)次方数找出来,\(k\ge 3\)。这种数最多只有\(10^6\times60\)个。
查询时直接在表上二分,再加上\([2,n]\)以内的所有完全平方数。由于sqrt
的精度较高,是可以\(O(\log10^6\times60)\)之内找出来的。
最后再减一下就行了。
代码:
// Code by H~$~C
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
vector<long long> nums;
void init() {
static const long long mx = 1e18;
nums.clear();
for (long long i = 2; i <= 1000000; ++i) {
long long x = i * i;
while (true) {
if (mx / x < i) break;
x *= i;
long long xx = sqrt(x);
if (xx * xx != x)
nums.push_back(x);
}
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
}
void solve() {
long long n;
cin >> n;
long long ans = n - int(sqrt(n));
int it = upper_bound(nums.begin(), nums.end(), n) - nums.begin();
cout << ans - it << endl;
}
int main() {
init();
int tests;
cin >> tests;
while (tests--) solve();
return 0;
}