A - Function Height
题意:
在平面直角坐标系中有(2n+1)个点,分别是(P_i=(i,0),0le ile 2n)。对于每个奇数(i),你一个操作可以将(P_i)向上移动(1)个单位。要让你用若干次操作使得操作后所有三角形的面积为(k)。问你操作后的(minlimits_{1le ile n}Y_{P_i}),其中(Y_p)表示点(p)的纵坐标。
题解:
显然要让所有点的横坐标尽量平均。又因为(P_i)的纵坐标每增加(1),总面积就增加(1)。所以总面积就等于所有增加的高度。所以答案应为(⌈dfrac{k}{n}⌉)
Code:
// 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'!!!mod2=1)时,只能将最后一步"好的"拆成两步。若到达了((n,m+1)),那么可以在最后一步之前一直围着((n,m))绕弯子,只有最后一步不是"好的"。
Code:
// 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)个数字(1sim 9)。给定([l,r],1le l,rle 10^{18}),问有多少个(x)使得(l le x le r),且(x)是"好数"。多次查询。
题解:
打表。预处理出所有的"好数",这个时间复杂度是(O((18*9)^3))的。然后每一次查询直接在打好的表上二分即可。
Code:
// 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 imes 10^5),数列中每个元素的值为([1,10^9])的整数。你需要将两个数列都分割成(k)份,使得每一份的元素和对应相等。如果可行,输出最多将序列分割成多少份,如果不可行,输出-1
。
题解:
如果(sumlimits_{i=1}^nA_i
eqsumlimits_{i=1}^mB_i),则输出-1
。接着用双指针,若当前数组(A)上的区间的和等于数组(B)上的区间和,则答案(+1),更新两个数组上的左区间。
Code:
// 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)条线段,求这些线段覆盖的整点的个数。(1le nle 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) imes(p_3-p_4)=0)的话,那么就肯定没有交点。
设交点为(p_0=(x,y)),则满足((p_1-p_0) imes(p_2-p_0)=0,(p_3-p_0) imes(p_4-p_0)=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),则最后可求得:
至于如何判断点(p_0)是否在线段(p_1,p_2)上,只需要((p_0-p_1) imes(p_0-p_2)=0),且((p_0-p_1)cdot(p_0-p_2)<0)即可。
有一个要特别注意的是这题如果你用double
写,会特别卡精度。
Code:
// 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
", 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
", ans);
return 0;
}
F - Relatively Prime Powers
题意:
对于一个数(x),当且仅当将其分解后各个质因数的指数的最大公约数为(1)时,我们称这个数是"好的"。多组询问,每次询问([2,n])区间内有多少数是"好的"。(2le nle 10^{18})
题解:
观察到若数(x)不是"好的",则(x=p^k),其中(p,k)是整数。
所以可以打表,将(10^{18})内所有(k)次方数找出来,(kge 3)。这种数最多只有(10^6 imes60)个。
查询时直接在表上二分,再加上([2,n])以内的所有完全平方数。由于sqrt
的精度较高,是可以(O(log10^6 imes60))之内找出来的。
最后再减一下就行了。
Code:
// 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;
}
G - Sources and Sinks
题意:
有一张DAG,定义source为入度为零的点,sink为出度为零的点。保证source和sink的个数相等且不超过(20)。定义匹配一个source和一个sink为从sink向source连一条有向边。问是否任意一个source与sink之间的完美匹配都可以使原图变成强连通。(1le nle 10^6)
题解:
只要任何一个sink到达任何一个source,整个图就是强连通的。所以原问题可以转化为是否任意一个source与sink之间的完美匹配都可以使任何一个sink到达任何一个source。
设一共有(c)个source。
如果存在一个source的集合(S)所能到达的所有sink的集合(T),若(|S|ge |T|)且(|S| e c),则很显然,只要当(T)里面的所有sink连向(S)里面的部
分source,就可以保证从(T)里面的任何一个sink到不了(S)外面的任何一个source。此时答案就是No
。
如果不存在这样的(S),那么就答案是Yes
。证明如下:
- 设当前的source集合为(S),sink集合为(T),则因为(|S|<|T|),(T)中的至少(1)个点就可以到达(S)以外的source。设(Scup T)以外所有能到的点(=S')。同理,(S')所有能到达的source的集合(T')也能到达至少(1)个(S')以外的点。如此类推,最后(T)就能到达所有的source。
这样就直接预处理出每一个source所能到达的sink的集合,再枚举source的集合检查一下就好了。
Code:
// Code by H~$~C
#include <bits/stdc++.h>
using namespace std;
static const int Maxn = 1000005;
int n, m, c;
vector<int> source, sink;
vector<int> g[Maxn], rg[Maxn];
int sink_id[Maxn];
int cnt[21];
int dfs(int u) {
if (g[u].empty()) return 1 << sink_id[u];
int res = 0;
for (int v: g[u]) res |= dfs(v);
return res;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
rg[v].push_back(u);
}
for (int i = 1; i <= n; ++i) {
if (rg[i].empty()) source.push_back(i);
if (g[i].empty()) {
sink_id[i] = (int)sink.size();
sink.push_back(i);
}
}
c = (int)source.size();
for (int u: source) cnt[u] = dfs(u);
for (int mask = 1; mask < (1 << c) - 1; ++mask) {
int sinks = 0;
for (int i = 0; i < c; ++i)
if (mask & (1 << i))
sinks |= cnt[source[i]];
int len_mask = __builtin_popcount(mask);
int len_sinks = __builtin_popcount(sinks);
if (len_mask >= len_sinks) return puts("NO") & 0;
}
puts("YES");
return 0;
}