CF Educational Round 50 题解

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)。展开后可得出:

[(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) 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;
}

原文地址:https://www.cnblogs.com/libra9z/p/12416717.html