A. 简单判断
题意:输出一个长度为 3 3 3 的字符串的所有排列中有多少种字符串
只需要判断这 3 3 3 个字符中有多少个相同的即可
- 有 3 3 3 个相同的,答案为 1 1 1
- 有 2 2 2 个相同的,答案为 2 2 2
- 互不相同,答案为 3 ! = 6 3! = 6 3!=6
#include<bits/stdc++.h>
using namespace std;
string s;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> s;
sort(s.begin(), s.end());
if (s[0] == s[1] && s[1] == s[2]) cout << 1 << endl;
else if (s[0] == s[1] || s[1] == s[2]) cout << 3 << endl;
else cout << 6 << endl;
return 0;
}
B. 入度计算
题意:给定一棵树,判断树上是否存在一个点连接其他所有的节点
计算每个点的度数,判断是否有一个点的度数为 N − 1 N - 1 N−1 即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, cnt[N];
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
cnt[u]++; cnt[v]++;
}
for (int i = 1; i <= n; i++) {
if (cnt[i] == n - 1) {
cout << "Yes\n";
return 0;
}
}
cout << "No\n";
return 0;
}
C. 判断
题意:给定一个行数无穷列数为 7 7 7 的矩阵,判断给定矩阵是否是这个大矩阵的子矩阵
因为矩阵中的数不重复,所以给定一个 B 1 , 1 B_{1, 1} B1,1 我们可以计算得到它所在的行 r r r 和列 c c c
接下来判断整个矩阵 B B B 即可
注意 r + M − 1 ≤ 7 r + M - 1\leq 7 r+M−1≤7 需要满足
#include<bits/stdc++.h>
using namespace std;
int n, m, b[10010][8];
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> b[i][j];
}
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= m; j++) {
if (j < m && b[i][j] + 1 != b[i][j + 1]) {
cout << "No\n";
return 0;
}
if (b[i][j] + 7 != b[i + 1][j]) {
cout << "No\n";
return 0;
}
}
}
int v = b[1][1] % 7 == 0? 7: b[1][1] % 7;
if (v + m - 1 <= 7) cout << "Yes\n";
else cout << "No\n";
return 0;
}
D. 构造数据结构
题意:给定数字 1 , 2 , 3 , . . . , N 1, 2, 3, ..., N 1,2,3,...,N,需要进行一下操作
- 以 x x x 结尾的组和以 y y y 开头的组合并(前后顺序不变)
- x , y x, y x,y 所在的组分开( x , y x, y x,y 中间是断点)
- 查询 x x x 所在的组的所有人(按顺序输出)
我们只需要维护两个数组即可,记为 p r e , n x t pre, nxt pre,nxt
p r e [ i ] pre[i] pre[i] 表示第 i i i 个人前面的那个人, n x t [ i ] nxt[i] nxt[i] 表示第 i i i 个人后面的那个人,如果没人就是 0 0 0
对于 1 1 1 操作: p r e [ y ] = x , n x t [ x ] = y pre[y] = x, nxt[x] = y pre[y]=x,nxt[x]=y
对于 2 2 2 操作: p r e [ y ] = 0 , n x t [ x ] = 0 pre[y] = 0, nxt[x] = 0 pre[y]=0,nxt[x]=0
对于 3 3 3 操作:先跳 p r e pre pre 数组按序取出所有 x x x 前面的数,再按 n x t nxt nxt 数组取出所有 x x x 后面的数,再合并
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int nxt[N], pre[N], n, q;
vector<int> ans;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> q;
for (int i = 1, u, v, ty; i <= q; i++) {
cin >> ty;
if (ty == 1) {
cin >> u >> v;
nxt[u] = v;
pre[v] = u;
}
else if (ty == 2) {
cin >> u >> v;
nxt[u] = 0;
pre[v] = 0;
}
else {
cin >> u;
ans.clear();
int now = u;
while (now) {
ans.push_back(now);
now = pre[now];
}
reverse(ans.begin(), ans.end());
now = nxt[u];
while (now) {
ans.push_back(now);
now = nxt[now];
}
cout << ans.size();
for (auto it: ans) {
cout << " " << it;
}
cout << '\n';
}
}
return 0;
}
E. 最大子段分配
对于一个 7 7 7,可以抽象为两个斜率, y − 1 x , y x − 1 \frac{y - 1}{x}, \frac{y}{x - 1} xy−1,x−1y
在一次选择中,如果没有斜率在 ( y − 1 x , y x − 1 ) (\frac{y - 1}{x}, \frac{y}{x - 1}) (xy−1,x−1y) 内,那么我们就说这个 7 7 7 是满足条件的
也就是说对于每个点 ( x i , y i ) (x_i, y_i) (xi,yi) 都可以抽象为一个区间 ( y i − 1 x i , y i x i − 1 ) (\frac{y_i - 1}{x_i}, \frac{y_i}{x_i - 1}) (xiyi−1,xi−1yi)
那么我们的任务就变成了:
给定 N N N 个区间,找到最多的两两不想交区间个数
那么这个问题就变成一个经典的问题了,两种解决方式:
- 贪心,按右端点进行排序,贪心去取
- 动态规划
相比而言贪心会好写很多
一些实现细节:
- 对于浮点运算会丢精度,但是以一个 p a i r pair pair 带入运算的话又太麻烦
- 一个比较好的解决方式是,先按斜率排序,再离散化,那么这 N N N 个区间的端点就都是整数了
- 注意上面说的按斜率排序并不需要计算出斜率,而是以一个 p a i r pair pair 的形式存储vhu并排序
时间复杂度: O ( N log N ) O(N\log N) O(NlogN)
#include<bits/stdc++.h>
using namespace std;
int n;
struct Vec {
int x, y;
Vec(int xx, int yy) {x = xx; y = yy;}
bool operator<(const Vec& b) const {
return 1ll * b.y * x < 1ll * b.x * y;
}
};
vector<Vec> vt, rec;
vector<pair<int, int>> v;
map<Vec, int> mp;
int cnt = 0;
bool cmp(pair<int, int> a, pair<int, int> b) {
if (a.second == b.second) return a.first < b.first;
else return a.second < b.second;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 0, x, y; i < n; i++) {
cin >> x >> y;
vt.push_back({x - 1, y}); vt.push_back({x, y - 1});
rec.push_back({x - 1, y}); rec.push_back({x, y - 1});
}
sort(vt.begin(), vt.end());
for (auto& it: vt) {
if (mp.count(it)) continue;
mp[it] = ++cnt;
}
for (int i = 0; i < n; i++) {
v.push_back({mp[rec[i << 1]], mp[rec[i << 1| 1]]});
}
sort(v.begin(), v.end(), cmp);
int id = 0, ans = 1;
for (int i = 0; i < n; i++) {
if (v[i].first >= v[id].second) {
id = i; ans++;
}
}
cout << ans << endl;
return 0;
}
F. 动态规划
首先一种很常见的思路是将所有字符串进行排序,从小到大出现前 K K K 个
但这样不行,因为比如
2 2
b
ba
能够有效保留一个相对大小关系并且不会出现以上情况的一种排序方式是
S i + S i + 1 ≤ S i + 1 + S i S_i + S_{i + 1}\leq S_{i + 1} + S_i Si+Si+1≤Si+1+Si
但如果只这样做的话也是不对的,因为
2 1
b
ba
我们可以结合上面的想法从后向前 d p dp dp
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示在 i , i + 1 , . . . , N i, i + 1, ..., N i,i+1,...,N 这些字符串中选 j j j 个进行拼接,产生的最小子串
那么有以下转移:
- d p [ i ] [ j ] ← d p [ i + 1 ] [ j ] dp[i][j]\leftarrow dp[i + 1][j] dp[i][j]←dp[i+1][j]
- d p [ i ] [ j ] ← S [ i ] + d p [ i + 1 ] [ j − 1 ] dp[i][j]\leftarrow S[i] + dp[i + 1][j - 1] dp[i][j]←S[i]+dp[i+1][j−1]
时间复杂度: O ( N K 2 max ∣ S i ∣ ) O(NK^2\max|S_i|) O(NK2max∣Si∣)
#include<bits/stdc++.h>
using namespace std;
const int N = 55;
string dp[N][N], s[N];
int n, k;
bool cmp(string a, string b) {
return a + b < b + a;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
sort(s + 1, s + n + 1, cmp);
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= n - i + 1; j++) {
if (!dp[i + 1][j].empty()) dp[i][j] = min(dp[i + 1][j], s[i] + dp[i + 1][j - 1]);
else dp[i][j] = s[i] + dp[i + 1][j - 1];
}
}
cout << dp[1][k] << endl;
return 0;
}