ABC371 解题报告(A-E)

A

题意

给定 A,B 之间,B,C 之间,A,C 之间的不等关系(大于或小于),问 A,B,C 中排第二大的数是哪一个。

解法

乍一看没啥思路,原来正解是打表(8 种情况秒了)。

     if (a == "<" && b == "<" && c == "<") {
         cout << "B\n";
     } else if (a == "<" && b == "<" && c == ">") {
         cout << "C\n";
     } else if (a == "<" && b == ">" && c == "<") {
         cout << "B\n";
     } else if (a == "<" && b == ">" && c == ">") {
         cout << "A\n";
     } else if (a == ">" && b == "<" && c == "<") {
         cout << "A\n";
     } else if (a == ">" && b == "<" && c == ">") {
         cout << "B\n";
     } else if (a == ">" && b == ">" && c == "<") {
         cout << "C\n";
     } else if (a == ">" && b == ">" && c == ">") {
         cout << "B\n";
     }

B

题意

某城依次有 m 个小孩出生,第 i 个小孩出生在第 A_i 个家庭,且有性别,问该小孩是不是该家庭的第一个儿子。

解法

很简单,开一个标记数组来记录每一个家庭是否有过儿子,每次按照题意判断即可。

     cin >> n >> m;
     for (int i = 1; i <= m; i ++) {
         cin >> a[i]; char c; cin >> c;
         if (c == 'F') {
             cout << "No\n";
             continue;
         }
         if (!vis[a[i]]) cout << "Yes\n";
         else cout << "No\n";
         vis[a[i]] = true;
     }

C

题意

给定两个 N\ (N\leq 8) 个节点的无向简单图 G,H,每次可以对图 H 进行一次操作 (i,j):

  • 如果 H 含有边 (i,j),就将其删去,代价为 A_{i,j}。

  • 如果 H 不含边 (i,j),就将其加上,代价为 A_{i,j}。

问通过这种操作,将两个图变成同构图的最小代价。

解法

乍一看很可怕,原来可以枚举。从同构图的定义入手,先 O(n!) 来枚举点的对应关系。

每枚举一种对应关系,就去看邻接矩阵对应位置是否相等,不相等就进行操作。显然,操作之间相互独立。

 #include <bits/stdc++.h>
 using namespace std;
 ​
 typedef long long ll;
 ll n, mg, mh, A[10][10];
 bool G[10][10], H[10][10];
 ll res[10], used[10], ans = 1e18;
 void dfs(ll dep) {
     if (dep > n) {
         ll cost = 0;
         for (int i = 1; i <= n; i ++)
             for (int j = 1; j <= n; j ++)
                 if (G[res[i]][res[j]] != H[i][j])
                     cost += A[i][j];
         ans = min(ans, cost);
         return;
     }
     for (int i = 1; i <= n; i ++) {
         if (!used[i]) {
             used[i] = true;
             res[dep] = i;
             dfs(dep + 1);
             used[i] = false;
         }
     }
 }
 ​
 int main() {
     ios::sync_with_stdio(false);
     cin >> n >> mg;
     for (int i = 1; i <= mg; i ++) {
         ll u, v; cin >> u >> v;
         G[u][v] = G[v][u] = 1;
     }
     cin >> mh;
     for (int i = 1; i <= mh; i ++) {
         ll u, v; cin >> u >> v;
         H[u][v] = H[v][u] = 1;
     }
     for (int i = 1; i <= n; i ++)
         for (int j = i + 1; j <= n; j ++)
             cin >> A[i][j];
     dfs(1); cout << ans << "\n";
     return 0;
 } 

D

题意

现在有 N 条信息,每条信息形如:点 X_i 上有 P_i 个人。保证 X_i 单调递增。

现在有 Q 次询问,每次询问形如:L_i,R_i,问区间 [L_i,R_i] 当中的点上有多少个人。

解法

因为 X_i 递增,所以可以二分。对于每次询问,用 lower_boundupper_bound 求出 X 数组中位于区间内的所有点,然后用前缀和算一下 P 的区间和即可。

     for (int i = 1; i <= n; i ++)
         s[i] = s[i-1] + p[i];
     cin >> q;
     while (q--) {
         ll ql, qr; cin >> ql >> qr;
         ll first_idx = lower_bound(x + 1, x + n + 1, ql) - x;
         ll last_idx = upper_bound(x + 1, x + n + 1, qr) - x - 1;
         cout << s[last_idx] - s[first_idx - 1] << "\n";
     }

E

题意

定义 f(i,j) 表示 A_i,A_{i+1},\dots,A_r 当中不同的值的个数。求 \sum\limits_{i=1}^{n}\sum \limits_{j=i}^{n}f(i,j)。

题解

非常经典的二维 \sum 问题。

暴力

枚举起点,把终点游标一格一格向右移动,每次加进来一个数,初始化标记数组为全 0,每遇到一个数,如果它的标记是 0,就是一个没出现过的数,答案加一,这样 O(n^2) 累加。

 ll n, a[N]; bool vis[N];
 ​
     ll ans = 0;
     for (int st = 1; st <= n; st ++) {
         for (int j = 1; j <= n; j ++) vis[j] = false;
         ll cnt = 0;
         for (int j = st; j <= n; j ++) {
             if (!vis[a[j]]) cnt ++;
             vis[a[j]] = true;
             ans += cnt;
         }
     }
     cout << ans << "\n";
正解

跟暴力似乎没太大关系。因为值域是 [1,n],是很小的。对于一数 i,记录它在数组中出现的所有位置 pt。

很显然,有多少个区间包含这个值 i,它对答案的贡献就是多少。下面问题转化为算出多少区间包含值 i。

假设有 m 个位置是 i,这 m 个位置的下标分别是 pt_1,pt_2,\dots,pt_m,那么那些被包含在两个位置中间空隙里的区间不包含 i。正难则反,一共有 \frac{n(n+1)}{2} 个区间,对于每个长度为 k 的“空隙“,其中会存在 \frac{k(k+1)}{2} 个不包含 i 的区间,把他们从总和当中减去即可。

 ll n, a[N];
 vector<ll> G[N];
 ​
     cin >> n;
     for (int i = 1; i <= n; i ++) {
         cin >> a[i];
         G[a[i]].push_back(i);
     }
 ​
     ll ans = 0;
     for (int num = 1; num <= n; num ++) {
         if (G[num].empty()) continue;
         ll m = G[num].size(), con = calc(n);
         con -= calc(G[num][0] - 1);
         for (int i = 1; i < m; i ++)
             con -= calc(G[num][i] - G[num][i-1] - 1);
         con -= calc(n - G[num][m-1]);
         ans += con;
     }
     cout << ans << "\n";
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值