2020牛客暑假补题

第一场

I题 1or2

这个是一般图匹配的题目,需要学习下带花树算法,博客如下

https://blog.csdn.net/weixin_45735431/article/details/107392327

主要难点就是分出虚点,把节点的度不是1的图转为度都为1的图,在进行一般图匹配
至于如何分虚点,博客如下

https://www.cnblogs.com/xiongtao/p/11189452.html

代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 2e3 + 10;
const int M = 5e5 + 10;

struct node {
  int to, nxt;
} g[M];
int head[N], cnt;
int vis[N], match[N], f[N], pre[N], Id, id[N];
// vis[i]: 0(未染色) 1(黑色) 2(白色)
// match[i]: i的匹配点
// f[i]: i在带花树中的祖先
// pre[i]: i的非匹配边的另一点
// id: 找LCA用
// g数组: 存关系
int n, m, ans, u, v;
queue<int> q;
int U[N], V[N];
int h;

void init() {
  Id = ans = cnt = 0;
  for (int i = 1; i <= (n+m)*2; i++) {
    head[i] = -1, id[i] = match[i] = 0;
  }
}

void add(int u, int v) { g[cnt].to = v, g[cnt].nxt = head[u], head[u] = cnt++; }

int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }

int lca(int x, int y) {
  for (++Id;; swap(x, y)) {
    if (x) {
      x = find(x);
      if (id[x] == Id)
        return x;
      else
        id[x] = Id, x = pre[match[x]];
    }
  }
}

void blossom(int x, int y, int l) {  // l为x,y的最近公共祖先(同一个花)
  while (find(x) != l) {
    pre[x] = y, y = match[x];
    if (vis[y] == 2) vis[y] = 1, q.push(y);
    if (find(x) == x) f[x] = l;
    if (find(y) == y) f[y] = l;
    x = pre[y];
  }
}

bool aug(int s) {
  for (int i = 1; i <= h; i++) {
    vis[i] = pre[i] = 0;
    f[i] = i;
  }
  while (!q.empty()) q.pop();

  q.push(s), vis[s] = 1;
  while (!q.empty()) {
    u = q.front();
    q.pop();
    for (int i = head[u]; ~i; i = g[i].nxt) {
      v = g[i].to;
      if (find(u) == find(v) || vis[v] == 2) continue;
      if (!vis[v]) {
        vis[v] = 2, pre[v] = u;
        if (!match[v]) {
          for (int x = v, last; x; x = last)
            last = match[pre[x]], match[x] = pre[x], match[pre[x]] = x;
          return true;
        }
        vis[match[v]] = 1, q.push(match[v]);
      } else {
        int LCA = lca(u, v);
        blossom(u, v, LCA), blossom(v, u, LCA);
      }
    }
  }
  return false;
}

int k;
int d[N];
vector<int> e[N];
void creat() {
  for (int i = 1; i < N; i++) e[i].clear();
  h = 0;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= d[i]; j++) {
      e[i].push_back(++h);
    }
  }
  //h--;
  for (int i = 1; i <= m; i++) {
    h++;
    for (int j = 0; j < e[U[i]].size(); j++) {
      add(e[U[i]][j], h);
      add(h, e[U[i]][j]);
    }
    h++;
    for (int j = 0; j < e[V[i]].size(); j++) {
      add(e[V[i]][j], h);
      add(h, e[V[i]][j]);
    }
    add(h - 1, h);
    add(h, h - 1);
  }
}

int main() {
  while (cin >> n >> m) {
    for (int i = 0; i <= cnt; i++) g[i].to = 0, g[i].nxt = 0;
    memset(match, 0, sizeof(match));
    memset(id, 0, sizeof(id));
    memset(f, 0, sizeof(f));
    memset(pre, 0, sizeof(pre));
    memset(head, 0, sizeof(head));
    memset(vis, 0, sizeof(vis));
    ans = 0;
    k = 0;
    init();
    for (int i = 1; i <= n; i++) cin >> d[i];
    int a = m;
    while (a--) {
      cin >> u >> v;
      U[++k] = u;
      V[k] = v;
      /*add(u, v);
      add(v, u);*/
    }
    creat();
    for (int i = 1; i <= h; i++) {
      if (!match[i] && aug(i)) ans++;
    }
    if (ans == h/2)
      cout << "Yes";
    else
      cout << "No";
    cout << endl;
  }
  return 0;
}

J题 Easy Integration

第一场的数论题之一,名字说是easy,但是推公式还是有点麻烦
题意大概是让你求对(x-x*x)^n积分
这个可以通过分部积分推公式
给一下大佬的推算过程
在这里插入图片描述
博客为

https://blog.csdn.net/qq_44607936/article/details/107308777?%3E

然后就是求这个式子了
我们需要求n!,这个前缀和处理一下,注意下乘法取模
然后对于除法,使用费马小定理取模就可以了
代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int maxn = 2e6 + 10;

ll q_pow(ll a, ll b) {
  ll sum = 1;
  while (b) {
    if (b & 1) {
      sum = (sum * a) % mod;
    }
    a = (a * a) % mod;
    b /= 2;
  }
  return sum % mod;
}

int n;
ll f[maxn];
int main() {
  f[0] = 1;
  for (int i = 1; i <= maxn; i++) {
    f[i] = (f[i - 1] * i) % mod;
  }
  while (cin >> n) {
    cout << (((f[n] * f[n]) % mod) * q_pow(f[2 * n + 1], mod - 2)) % mod << endl;
  }
  return 0;
}

第二场

B题 Boundary

这一题的大概意思就是给你一个原点(0,0)和一些点,问画一个圆(一定过原点),在园的边界上最多可以有多少点
这里有一个非常朴素的做法,不需要像题解那样处理高精度问题(据说要处理到1e-10),也是这题大多数的做法
我们具体做法通过三点确定一条直线来2个点2个点地画圆(两个for循环即可),然后使用map数组来维护圆心,如果遇到了相同的圆点,我们就把map数组加1
至于对于顶点是否会重复访问,我们只需要在第一个for循环里面重置map数组即可,简略证明如下

当我们选定一个节点,并通过这个节点在第二个for循环找第三个节点(还有个原点),这时候,我们对于一个园,当求出一个圆心之后,我们知道了圆心,原点,第一个循环中的点三个点,确定了一个园,所以在第二重循环中一个点所在的圆下个第一次循环就不会再出现
代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <vector>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int maxn = 2e3 + 10;

int n;
typedef struct {
  double x;
  double y;
}P;
typedef pair<double, double> p;

map<p, int> Map;
P circle[maxn];


p get_circle(double x1, double y1, double x2, double y2, double x3, double y3) {
  double a = 2 * (x2 - x1);
  double b = 2 * (y2 - y1);
  double c = x2 * x2 + y2 * y2 - x1 * x1 - y1 * y1;
  double d = 2 * (x3 - x2);
  double e = 2 * (y3 - y2);
  double f = x3 * x3 + y3 * y3 - x2 * x2 - y2 * y2;
  double x = (b * f - e * c) / (b * d - e * a);
  double y = (d * c - a * f) / (b * d - e * a);
  return p{x, y};
}

bool cmp(P a, P b) {
  if (a.x != b.x)
    return a.x < b.x;
  else
    return a.y < b.y;
}

double x, y;
int ans = 0;
int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> x >> y;
    circle[i].x = x;
    circle[i].y = y;
  }
  sort(circle + 1, circle + n + 1, cmp);
  for (int i = 1; i <= n; i++) {
    Map.clear();
    for (int j = i + 1; j <= n; j++) {
      if (circle[i].x * circle[j].y == circle[i].y * circle[j].x) continue;
      p point = get_circle(0.0, 0.0, circle[i].x, circle[i].y, circle[j].x,
                           circle[j].y);
      ans = max(ans, ++Map[point]);
    }
  }
  cout << ans + 1;
  return 0;
}

C题 Cover the Tree

这题是给定一个树,询问要最少多少条链可以把这个树变成环
这是一道dfs序的题目,博客如下

https://blog.csdn.net/weixin_44235989/article/details/107332926?%3E

代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;

int n;
vector<int> g[maxn];
int num[maxn];
int sum;
int used[maxn];

void dfs(int s) {
  if (g[s].size() == 1) {
    sum++;
    num[sum - 1] = s;
  }
  for (int i = 0; i < g[s].size(); i++) {
    if (!used[g[s][i]]) {
      used[g[s][i]] = 1;
      dfs(g[s][i]);
    }
  }
  return;
}

int main() {
  cin >> n;
  for (int i = 1; i <= n - 1; i++) {
    int u, v;
    cin >> u >> v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  used[1] = 1;
  dfs(1);
  cout << (sum + 1) / 2 << endl;
  for (int i = 0; i < (sum + 1) / 2; i++) {
    cout << num[i] << ' ' << num[i + sum / 2] << endl;
  }
  return 0;
}

第四场

H题 Harder Gcd Problem

这一题题意大概是给定一个数n,求小于等于n的数所组成gcd(i,j)>1的最多对数,并且输出这些数对
对于这一题思考,我们很可以发现,
1.每一个素数只能和他的倍数所匹配
2.每个素数的倍数之间均可以两两配对
通过这两点,我们便可以使用贪心的方法做这道题
我们先使用欧拉筛筛出所有的素数,然后从小于n/2的最大素数开始求素数的倍数并且标记,如果在小于等于n范围内这个素数的倍数的个数(包括自身)是偶数,我们就直接加上个数/2;如果是奇数,我们把这个素数的2倍的数标记撤除,空余出这个数
为什么这样做是最优的呢?下面给出简略证明

首先我们通过小于等于n/2最大的素数来求倍数,第一点是只有素数的两倍小于等于n,它才有最少一个匹配数;第二点是越大的素数它的匹配数就越少,所以我们贪心的先解决最大素数的匹配
然后就是为什么我们求素数倍数可以最优。因为当我们求一个素数的倍数,并且从最大的素数开始,这时候每个素数只会遇到两种情况:倍数的个数是偶数和倍数的个数是奇数。如果是偶数就全部两两配对,奇数就留出这个数两倍的数,其他的两两配对。这时候就算我们匹配掉了后面的数,求解到前面奇数的时候,仍然是只会有奇偶个数区别。对于奇数我们把2倍数留下来,最后我们求到2的时候,所有剩下的数都是2的倍数,这样我们除了无法匹配的素数,最多只有1个数无法匹配(2倍数的个数为奇数时)

所以,我们就可以解决这道题,代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;


 int is_prime[maxn];  //用来筛取的数组,用作标记
 int prime[maxn];     //将素数存入其中
 int cnt = 0;         //记录素数的个数

 void sieve() {
  for (int i = 2; i < maxn; i++) {
    if (!is_prime[i]) prime[cnt++] = i;  //没有被标记便存入prime数组
    for (int j = 0; j < cnt; j++) {
      if (i * prime[j] >= maxn) break;    //当筛到最大数时,便停止
      is_prime[i * prime[j]] = 1;  //标记为非素数
      //停止条件,也是欧拉筛的核心
      if (i % prime[j] == 0) break;
    }
  }
}

int T;
int n;
int vis[maxn];
vector<int> List;
int main() { 
	sieve();
	cin >> T;
  while (T--) {
    List.clear();
    memset(vis, 0, sizeof(vis));
    cin >> n;
    int count = 0;
    for (int i = cnt - 1; i >= 0; i--) {
      if (prime[i] <= n / 2) {
        int ans = 0;
        for (int j = 1; j * prime[i] <= n; j++) {
          if (!vis[j * prime[i]]) {
            vis[j * prime[i]] = 1;
            ans++;
            List.push_back(j * prime[i]);
          }
        }
        if (ans % 2 == 1) {
          vis[List[List.size() - ans + 1]] = 0;
          List[List.size() - ans + 1] = List[List.size() - 1];
          List.pop_back();
        }
        count += ans / 2;
      }
    }
    printf("%d\n", count);
    for (int i = 0; i < List.size(); i += 2) {
      printf("%d %d\n", List[i], List[i + 1]);
    }
  }
  return 0;
}

第6场

B Binary Vector

这一题是一道数论题,给上公式的推导
在这里插入图片描述

在这里插入图片描述
最后得到f的递推式。然后需要注意的是,求1/(2^i)的逆元时,费马小定理超时,我们在这里这样x = x * y % mod;来计算

for (int i = 2; i <= 2e7; i++) {
    x = x * y % mod;
    ans[i] = ans[i - 1] * 2;
    a[i] = ((a[i - 1] - a[i - 1] * x) % mod + mod) % mod;
  }

然后输出f1 ^ f2 ^ …… ^ fn的值即可
代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 2e7 + 10;
const ll mod = 1e9 + 7;

int T;
int n;
ll a[maxn];
ll ans[maxn]; 
ll x = 5e8 + 4;
ll y = 5e8 + 4;
ll qpow(ll a, ll b) {
  ll sum = 1;
  while (b) {
    if (b & 1) {
      sum = sum * a % mod;
    }
    a = a * a % mod;
    b /= 2;
  }
  return sum % mod;
}

int main() {
  cin >> T;
  a[1] = 500000004;
  ans[1] = 1;
  for (int i = 2; i <= 2e7; i++) {
    x = x * y % mod;
    ans[i] = ans[i - 1] * 2;
    a[i] = ((a[i - 1] - a[i - 1] * x) % mod + mod) % mod;
  }
  for (int i = 2; i <= 2e7; i++) {
    a[i] = a[i - 1] ^ a[i];
  }
  while (T--) {
    cin >> n;
    cout << a[n] << endl;
  }
  return 0;
}

第八场

I Interesting Computer Game

题意大概是:给出n对数,你可以操作n次,每次操作只能在下面三种中选择一种,问最多可以选多少个不同的数字。

  • 什么都不做
  • 如果a[i]以前没选过,那么可以选择a[i]
  • 如果b[i]以前没选过,那么可以选择b[i]

对于这一题,我们可以将其转换为图来解决。
题目要求我们找出最多的不同数字,如果我们把ai和bi连起来,我们可以构成多个联通图。这个时候,就是在要求我们求出这些联通图最多的增广路
同时我们注意到,对于一个连通图,如果图中存在至少一个环,那么我们便可以选择这个联通图的所有顶点;如果不存在环,我们只能选择这个图顶点数减1个点。所以这题又转变为联通图记录顶点数加判环的问题

至于判环,这题有两种解决方式。
1.通过并查集来高效判环
2.通过dfs来判环

这里是使用第二种方法,具体就是在寻找增广路的过程中,如果遇到一个被标记过的点,并且上一个点不是从它转换过来的(用pre数组标记这个点是从哪个点寻找过来的),那么就是出现了环

然后需要对于数据进行一下离散化,这里使用unordered_map来离散化处理

代码如下

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;

unordered_map<ll, ll> a;
int vis[maxn];
int T;
int n;
int cnt;
ll num1[maxn], num2[maxn];
vector<int> vec[maxn];
int sum;
int ans;
int flag;
int pre[maxn];

void dfs(int x) {
  ans++;
  for (int i = 0; i < vec[x].size(); i++) {
    if (!vis[vec[x][i]]) {
      vis[vec[x][i]] = 1;
      pre[vec[x][i]] = x;
      dfs(vec[x][i]);
    } else if (pre[x] != vec[x][i]) {
      flag = 1;
    }
  }
}

int main() {
  cin >> T;
  int h = T;
  while (T--) {
    memset(pre, 0, sizeof(pre));
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i < maxn; i++) {
      vec[i].clear();
    }
    a.clear();
    scanf_s("%d", &n);
    sum = 0;
    cnt = 0;
    for (int i = 1; i <= n; i++) {
      scanf_s("%d%d", &num1[i], &num2[i]);
      if (!a.count(num1[i])) {
        a.emplace(num1[i], ++cnt);
      }
      if (!a.count(num2[i])) {
        a.emplace(num2[i], ++cnt);
      }
    }
    for (int i = 1; i <= n; i++) {
      vec[a[num1[i]]].push_back(a[num2[i]]);
      vec[a[num2[i]]].push_back(a[num1[i]]);
    }
    for (int i = 1; i <= cnt; i++) {
      if (!vis[i]) {
        vis[i] = 1;
        flag = 0;
        ans = 0;
        dfs(i);
        if (flag == 1) {
          sum += ans;
        } else
          sum += (ans - 1);
      }
    }
    printf("Case #%d: %d\n", h - T, sum);
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值