题号 | 标题 | 已通过代码 | 通过率 | tag | 难度 |
---|---|---|---|---|---|
A | 智乃的Hello XXXX | 点击查看 | 3023/3257 | 签到 | 500 |
B | 智乃买瓜 | 点击查看 | 1632/3604 | DP | 1300 |
C | 智乃买瓜(another version) | 点击查看 | 331/811 | DP | 1900 |
D | 智乃的01串打乱 | 点击查看 | 2851/4923 | 模拟 | 1000 |
E | 智乃的数字积木(easy version) | 点击查看 | 829/1948 | 模拟 | 1400 |
F | 智乃的数字积木(hard version) | 点击查看 | 31/264 | 并查集启发式合并 | 2300 |
G | 智乃的树旋转(easy version) | 点击查看 | 438/1443 | 平衡树 | 1700 |
H | 智乃的树旋转(hard version) | 点击查看 | 77/179 | 平衡树 | 2000 |
I | 智乃的密码 | 点击查看 | 746/4260 | 贪心模拟二分 | 1500 |
J | 智乃的C语言模除方程 | 点击查看 | 109/861 | 数学 | 2000 |
K | 智乃的C语言模除方程(another version) | 点击查看 | 16/95 | 数学 | 2200 |
L | 智乃的数据库 | 点击查看 | 1078/2757 | 模拟数据结构 | 1500 |
A
B
分组背包DP模版题
设 d[i][j] 表示前 i 个西瓜,凑出重量 j 的方案数。
那么有动态转移方程:
第 i 个瓜有三种转移策略:
- 不买,累加
- 买半个,累加
- 买一整个,累加
将这三个加到一起,就是 d[i][j] 了。
注意到,计算 d[i][j] 时,只会用到 d[i-1] 中更小的 j,所以可以使用滚动数组来压缩,倒序枚举 j 即可。(当然这一步省掉也是OK的)
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 1010, mod = 1e9 + 7;
int n, m, w[N];
ll d[N];
int main(){
cin >> n >> m;
d[0] = 1;
rep(i,1,n) {
scanf("%d", &w[i]);
for(int j = m; j >= 0;j--){
if(j >= w[i]) d[j] = (d[j] + d[j - w[i]]) % mod;
if(j >= w[i] / 2) d[j] = (d[j] + d[j - w[i] / 2]) % mod;
}
}
rep(i,1,m) printf("%lld ", d[i]);
puts("");
}
C
DP
由于只有买半个重量为 2 的西瓜,才有可能使得总重量为 1,所以 d[1] 就是西瓜重量为 2 的数量。
然后试图从 DP 数组中删除这些瓜的贡献:
for(int t = 1; t <= d[1]; t ++){
for(int j = 1; j <= m; j ++) {
if(j >= w / 2) d[j] = (d[j] - d[j - 1] + mod) % mod;
if(j >= w) d[j] = (d[j] - d[j - 2] + mod) % mod;
}
}
删除掉以后,当前 d[2] 的值就是西瓜重量为 4 的数量,然后再紧接着消除即可。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 1010, mod = 1e9 + 7;
int n, m, a[N], c[2 * N];
ll d[N], f[N];
int main(){
scanf("%d", &m);
rep(i,1,m) {
scanf("%lld", &d[i]);
}
d[0] = 1;
for(int i = 1; i <= m; i++){
int w = i * 2;
c[w] = d[i];
n += c[w];
for(int t = 1; t <= c[w]; t ++){
for(int j = 1; j <= m; j++){
if(j >= w / 2) d[j] = (d[j] - d[j - w / 2] + mod) % mod;
if(j >= w) d[j] = (d[j] - d[j - w] + mod) % mod;
}
}
}
printf("%d\n", n);
for(int i=2;i<=2*m;i+=2){
while(c[i]) {
printf("%d ", i);
c[i] --;
}
}
return 0;
}
D
签到题
很简单,这里提供两种思路:
- 可以排序后与原串对比,如果不一样则输出,否则倒序之后输出。
- 找到随便一个与 s[0] 不同的字符,与之交换输出。
```cpp
include
using namespace std;define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
define per(i,j,k) for(int i=int(j);i>=int(k);i—)
typedef long long ll; int n; string s, t;
int main(){ cin >> n >> s; t = s; sort(s.begin(), s.end()); if(s != t) { cout << s << endl; } else{ reverse(s.begin(), s.end()); cout << s << endl; } return 0; }
```cpp
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int x = s.find(s[0] ^ 1); // 由于字符中只有 '0'(ASCII 48) 和 '1' (ASCII 49),所以可以通过异或来指定
std::swap(s[0], s[x]);
std::cout << s << "\n";
return 0;
}
E
计算一个字符串表示的数字取模后的值:
string s;
cin >> s;
long long rs = 0;
for(auto &c : s) {
rs = (rs * 10 + c - '0') % mod;
}
cout << rs << endl;
对于原串中颜色相同的一个区间 [l, r](用双指针维护一下),然后对 [l, r] 按照降序排列,最后计算即可。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 100010, mod = 1e9 + 7;
int n, m, k;
char s[N], t[N];
int c[N];
int get() {
memcpy(t, s, sizeof t);
int l = 1;
rep(i, 1, n) {
// 当 i == n 或 i 和 i + 1 颜色不同时,i 为区间右端点
if(i == n || c[i] != c[i + 1]) {
sort(t + l, t + i + 1, greater<char>()); // 对 [l, i] 降序排序
l = i + 1; // 设置新的区间左端点
}
}
ll sum = 0;
rep(i,1,n) {
sum = sum * 10 + (t[i] - '0');
sum %= mod;
}
return sum;
}
int main(){
scanf("%d%d%d", &n, &m, &k);
scanf("%s", s + 1);
rep(i,1,n) {
scanf("%d", &c[i]);
}
printf("%d\n", get());
while(k--){
int x, y;
scanf("%d%d", &x, &y);
rep(i,1,n) if(c[i] == x) c[i] = y; // 修改颜色
printf("%d\n", get());
}
return 0;
}
F
题意与E题相同,不同的是变色次数变成了 。
样例: 字符:12345 初试颜色:1 2 3 4 3
每个颜色分配一个集合:
颜色1:[1, 1]
颜色2:[2, 2]
颜色3:[3, 3],[5, 5]
颜色4:[4, 4]
每种颜色对应一个集合,集合中包含若干区间,每个区间的颜色都应当统一。
当颜色 x 和染成颜色 y 时,等同于将集合 x 中的元素移动到集合 y 中,并对集合 y 中的区间进行合并。
我们每次将小的集合向大的集合合并,这样的操作,总体复杂度为 ,因为每次合并,集合大小至少扩大二倍,所以最终每个元素的合并次数不超过 。
具体合并时,假设 x 是较小的集合,遍历其中的每个区间,尝试与 y 中与之相邻的区间进行合并(使用 lower_bound等二分快速找到相邻集合)。
我们可以用一个结构体来描述一个区间:
struct Node {
int l, r; // 区间左右端点
int cnt[10]; // cnt[x] 表示该区间中 x 的个数
ll sum; // 该区间对整体答案的贡献
Node(){ // 构造函数,初始化
l = r = sum = 0;
memset(cnt, 0, sizeof cnt);
}
void get(){ // 更新该区间对最终答案的贡献
sum = 0;
int suf = n - l + 1; // suf 为当前区间所处的后缀长度
for(int i = 9; i >= 0; i--){ // 贪心的,优先考虑拼凑最大的,从9开始倒序考虑
// 这里要拼凑 cnt[i] 个 i,可以用 10^cnt[i] - 1 来表示 cnt[i] 个 9 的数值,然后除以 9 再乘 i
// 比如 5 个 4 即 44444 等于 (10^5 - 1) / 9 * 4 = 99999 / 9 * 4
// 模意义下,除法要变为乘逆元
// 最后还要乘上 10^suf
suf -= cnt[i];
// (10^cnt - 1) / 9 * k
sum = (sum + (P[cnt[i]] - 1 + mod) % mod * inv9 % mod * i % mod * P[suf] % mod) % mod;
}
}
// 重载 < 运算符,用于二分查找
bool operator < (const Node & o) const {
return l < o.l;
}
};
因此,可以方便的实现集合合并,顺便进行最终答案的修改。由于启发式合并以及单次合并要进行二分查找,所以总体复杂度为。
#include<bits/stdc++.h>
using namespace std;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 100010, mod = 1e9 + 7;
int n, m, k, col[N];
ll P[N], inv9, rs;
ll ksm(ll a, ll b){
ll rs = 1;
for(;b;b >>= 1) {
if(b & 1) rs = rs * a % mod;
a = a * a % mod;
}
return rs;
}
char s[N];
struct Node {
int l, r;
int cnt[10];
ll sum;
Node(){
l = r = sum = 0;
memset(cnt, 0, sizeof cnt);
}
void get(){
sum = 0;
int suf = n - l + 1;
for(int i = 9; i >= 0; i--){
suf -= cnt[i];
sum = (sum + (P[cnt[i]] - 1 + mod) % mod * inv9 % mod * i % mod * P[suf] % mod) % mod;
}
}
bool operator < (const Node & o) const {
return l < o.l;
}
};
set<Node> st[N];
void get(){
Node t;
t.l = 1; t.sum = 0;
rep(i,1,n) {
t.cnt[s[i] - '0'] ++;
if(i == n || col[i] != col[i + 1]) {
t.r = i;
t.get();
rs = (rs + t.sum) % mod;
st[col[i]].insert(t);
t.l = i + 1;
memset(t.cnt, 0, sizeof t.cnt);
}
}
}
void merge(int x, int y){
if(st[x].size() < st[y].size()) {
swap(st[x], st[y]);
}
// 集合合并
for(auto t : st[y]) {
auto it = st[x].upper_bound(t);
if(it != st[x].end() && (*it).l == t.r + 1) {
Node p = *it;
st[x].erase(it);
rs = (rs - t.sum + mod - p.sum + mod) % mod;
for(int i = 9; i >= 0; i--) t.cnt[i] += p.cnt[i];
t.r = p.r;
t.get();
rs = (rs + t.sum) % mod;
}
it = st[x].lower_bound(t);
if(it != st[x].begin() && ((*--it).r == t.l - 1)) {
Node p = *it;
st[x].erase(it);
rs = (rs - t.sum + mod - p.sum + mod) % mod;
for(int i = 9; i >= 0; i--) t.cnt[i] += p.cnt[i];
t.l = p.l;
t.get();
rs = (rs + t.sum) % mod;
}
st[x].insert(t);
}
swap(st[x], st[y]);
st[x].clear();
}
int main(){
scanf("%d%d%d", &n, &m, &k);
scanf("%s", s + 1);
P[0] = 1;
rep(i,1,n) P[i] = P[i-1] * 10 % mod;
inv9 = ksm(9, mod - 2);
rep(i,1,n) {
scanf("%d", &col[i]);
}
get();
printf("%lld\n", rs);
while(k --){
int x, y; scanf("%d%d", &x, &y);
if(x != y) merge(x, y);
printf("%lld\n", rs);
}
}
G
A 要进行右旋,旋转后 A 将变为 B 的父亲,假设实际树中 B 还有父亲 C,那么 C 的孩子将有原来的 B 替换为 A。
所以,给定两个树 A,B,如果只进行了最多一次旋转,我们只需要同时从两个树的根开始 DFS,如果遇到某个结点 X,X 在 A 树中的左孩子(或右孩子)是 H,而 X 在 B 树中的左孩子(或右孩子)是 G,那么说明是 G 进行了旋转。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 1010;
int n;
bool flag;
struct Tr{
int l[N], r[N];
int fa[N];
int rt;
// 初始化 fa 数组 和 rt(即 root)
void getf(){
rep(i,1,n) {
fa[l[i]] = i;
fa[r[i]] = i;
}
rep(i,1,n) {
if(fa[i] == 0) rt = i;
}
}
}A, B;
void dfs(int x) {
if(x == 0) return; // 为 0 直接返回,否则会无限递归
if(flag) return;
if(A.l[x] == B.l[x]) {
dfs(A.l[x]);
} else { // 左孩子不同
printf("%d\n", A.l[x]);
flag = true; return;
}
if(A.r[x] == B.r[x]) { // 右孩子不同
dfs(A.r[x]);
} else {
printf("%d\n", A.r[x]);
flag = true; return;
}
}
int main(){
scanf("%d", &n);
rep(i,1,n) {
scanf("%d%d", &A.l[i], &A.r[i]);
}
A.getf();
rep(i,1,n) {
scanf("%d%d", &B.l[i], &B.r[i]);
}
B.getf();
bool fg = false;
// 判断两个树是否一致
rep(i,1,n) {
if(A.l[i] == B.l[i] && A.r[i] == B.r[i]) continue;
fg = true; break;
}
if(!fg) {
printf("0\n");
return 0;
}
printf("1\n");
if(A.rt != B.rt) {
printf("%d\n", A.rt);
return 0;
}
dfs(A.rt);
return 0;
}
H
有两个树A,B。我们要对 B 进行复原成A。
第一步可以做的就是,判断 A 和 B 的根是否一致。
如果不一致,设 X 为 A 当前的根,在 B 中找到 X,然后将 X 向上旋转,直到 X 为当前的根。(实际处理过程要注意细节)
如果一致则分别递归左右两边。
递归后,当做子树问题来处理即可。根据二叉搜索树的性质,根结点如果一致,那么两边的结点集合也一定是相同的。
难点在于代码上。
#include<bits/stdc++.h>
using namespace std;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 1010;
int n;
bool flag;
struct Tr{
int l[N], r[N];
int fa[N];
int rt;
void getf(){
rep(i,1,n) {
fa[l[i]] = i;
fa[r[i]] = i;
}
rep(i,1,n) {
if(fa[i] == 0) {
rt = i;
l[0] = rt;
}
}
}
void rotate(int p) {
// 旋转代码不要写错,除了维护 l,r 还要维护 fa
int f = fa[p], anc = fa[f];
if(p == l[f]) { // 右旋
int rson = r[p];
l[f] = rson; fa[rson] = f;
r[p] = f; fa[f] = p;
if(f == l[anc]) l[anc] = p;
else r[anc] = p;
fa[p] = anc;
} else { // 左旋
int lson = l[p];
r[f] = lson; fa[lson] = f;
l[p] = f; fa[f] = p;
if(f == l[anc]) l[anc] = p;
else r[anc] = p;
fa[p] = anc;
}
rt = l[0];
}
}A, B;
vector<int> rs;
void dfs(int Art, int Brt) {
if(Art == 0) return;
// 如果当前子树中,A的根和B的根不一致,那么就要对B进行调整
if(Art != Brt){
// 不断的调整 B 中 Art 的位置,使得 Art 在A 和 B 中的父亲是一致的(也就是子树的根一致了)
while(A.fa[Art] != B.fa[Art]) {
B.rotate(Art);
rs.push_back(Art);
}
}
dfs(A.l[Art], B.l[Art]);
dfs(A.r[Art], B.r[Art]);
}
int main(){
scanf("%d", &n);
rep(i,1,n) {
scanf("%d%d", &A.l[i], &A.r[i]);
}
A.getf();
rep(i,1,n) {
scanf("%d%d", &B.l[i], &B.r[i]);
}
B.getf();
bool fg = false;
rep(i,1,n) {
if(A.l[i] == B.l[i] && A.r[i] == B.r[i]) continue;
fg = true; break;
}
if(!fg) {
printf("0\n");
return 0;
}
dfs(A.rt, B.rt);
printf("%d\n", (int) rs.size());
for(auto &x : rs) printf("%d ", x);
return 0;
}
I
首先判断得到每个字符它所属的类别,然后用前缀和 d[i][j] 表示前面 i 个字符中,类别 j 的个数。
假设 [L, R] 满足要求,那么对于所有的 l < L, [l, R] 都满足要求。
固定 R,求解最大的 L,使得 [L, R] 满足要求。然后考虑 R 递增,这样的 L 也是非严格递增的,所以可以用一个指针来维护 L。
每次求到的都是最短的区间 [L, R], 所以符合要求的长度区间为 [R - L + 1, R], 与题目输入的区间求交即可。
当然,L 也可以每次二分去求。
7ms
#include<bits/stdc++.h>
using namespace std;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 100010;
int n, l, r;
char s[N];
int d[N][4];
int get(int L, int R) {
return max(0, min(R, r) - max(L, l) + 1);
}
bool check(int L, int R) {
int cnt = 0;
rep(i,0,3) if(d[R][i] - d[L - 1][i] > 0) cnt ++;
return cnt >= 3;
}
int main(){
cin >> n >> l >> r;
scanf("%s", s + 1);
ll rs = 0;
int L = 1;
rep(i,1,n) {
int t;
if(s[i] >= 'a' && s[i] <= 'z') t = 0;
else if(s[i] >= 'A' && s[i] <= 'Z') t = 1;
else if(s[i] >= '0' && s[i] <= '9') t = 2;
else t = 3;
// memcpy 内存拷贝,直接赋值之前的
memcpy(d[i], d[i-1], sizeof d[i]);
d[i][t] ++;
// 贪心,尽量让 L 变得更大
while(L < i && check(L + 1, i)) L++;
if(check(L, i)) {
rs += get(i - L + 1, i); // [i - L + 1, i] 与 [l, r] 求交
}
}
printf("%lld\n", rs);
return 0;
}
二分(12ms):
#include<bits/stdc++.h>
using namespace std;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 100010;
int n, l, r;
char s[N];
int d[N][4];
int get(int L, int R) {
return max(0, min(R, r) - max(L, l) + 1);
}
int main(){
cin >> n >> l >> r;
scanf("%s", s + 1);
ll rs = 0;
rep(i,1,n) {
int t;
if(s[i] >= 'a' && s[i] <= 'z') t = 0;
else if(s[i] >= 'A' && s[i] <= 'Z') t = 1;
else if(s[i] >= '0' && s[i] <= '9') t = 2;
else t = 3;
memcpy(d[i], d[i-1], sizeof d[i]);
d[i][t] ++;
int L = -1, R = i - 1;
while(L < R) {
int m = L + R + 1 >> 1;
int mn = 0;
rep(j,0,3) mn += d[i][j] - d[m][j] > 0;
if(mn >= 3) {
L = m;
} else R = m - 1;
}
if(L != -1) {
rs += get(i - L, i);
}
}
printf("%lld\n", rs);
return 0;
}
J
,问有多少 x,满足
题目中有说,取模后的结果,正负号与 P 无关,只与 x 有关。所以一开始我们就可以将 P 直接取绝对值。
对于 [L,R] 和 [l,r],区间内可能正负数都有,但我们可以将负数对称过来当正数处理。
对于原本正数的部分:[ max(0, L), R] 和 [ max(0, l), r]
对于原本负数的部分:[max(0, -R), -L] 和 [max(0, -r), -l]
当然,如果 R < 0, 那么 [ max(0, L), R] 就是个不正确的区间,区间右端点小于左端点的特殊情况,可以直接返回 0 的结果。
然后,按照上面的分类,进行正数和负数部分计算时,0会被计算两次,所以可以将负数部分整体右移:[max(1, -R), -L] 和 [max(0, -r), -l](值域并不右移)
所以,现在问题简化为:
设 get(R, r) 表示有多少 0 <= x <= R 模 P 后 Q 满足 0 <= Q <= r。
那么直接调用 (get(R, r) - get(R, l - 1)) - (get(L - 1, r) - get(L - 1, l - 1)) 即可。
get(R, r) 如何实现,留给自己想象。
int p, l, r, L, R;
ll rs;
int get(int R, int r) {
if(R < 0 || r < 0) return 0;
r = min(r, p - 1);
return R / p * (r + 1) + min(R % p, r) + 1;
}
int get(int L, int R, int l, int r) {
if(R < L || r < l) return 0;
return (get(R, r) - get(R, l - 1)) - (get(L - 1, r) - get(L - 1, l - 1));
}
int main(){
cin >> p >> l >> r >> L >> R;
p = abs(p);
int rs = get(max(0, L), R, max(0, l), r)
+ get(max(1, -R), -L, max(0, -r), -l);
printf("%d\n", rs);
return 0;
}
K
,问有多少 x,满足
找不到切入点时,可以尝试打表找规律。下面是给出定理和证明:
如果有两个整数
由取模定义:
可以得到:
所以,如果有一个数列:,其取值 相同。那么 是一个等差数列。
例如,100 / 34 = 100 / 50 = 2,所以对于 {34, 35, 36, … 50},取模后构成等差数列: 100 % 34 = 32, 100 % 35 = 30, 100 % 36 = 28, … 100 % 50 = 0
所以,如果要求 [1, R] 中有多少 x 满足 P % x 在 [0, r] 范围内,可以使用数论分块快速计算。(即每一块内,构成一个等差数列)
其余的部分与 J 题类似。
const int inf = 1e9 + 3;
int p, l, r, L, R;
int get(int R, int r) {
if(R < 1 || r < 0) return 0;
int rs = 0;
// 对于 R > p 的情况,p % x 恒等于 p,单独统计答案
if(R > p) {
if(r >= p) rs += R - p;
R = p;
}
// 数论分块,复杂度 O(根号R)
for(int x = 1, y = 0; x <= R; x = y + 1) {
//定义 k = p / x,那么 [x, y] 中的所有值 i 都有 p / i = k,且 y 是满足这个条件的最大值,x是最小值。
y = min(R, p / (p / x));
// s 为等差数列首项,t 为末项
int s = p % y, t = p % x;
int cnt = y - x + 1;
// 计算等差数列中有多少数字在 [0, r] 中
if(t <= r) rs += cnt;
else if(s <= r) {
int d = (t - s) / (y - x);
rs += (r - s) / d + 1;
}
}
return rs;
}
int get(int L, int R, int l, int r) {
if(L > R || l > r) return 0;
return get(R, r) - get(R, l - 1) - get(L - 1, r) + get(L - 1, l - 1);
}
int main(){
cin >> p >> l >> r >> L >> R;
// 将 p < 0 的情况对称到 p >= 0, 值域 [l,r] 也要相应翻转
if(p < 0) {
p = -p;
l = -l, r = -r;
swap(l, r);
}
// 由于 p >= 0,所以 l >= 0 的情况才会被计算
l = max(0, l);
int rs = get(max(1, L), R, l, r)
+ get(max(1, -R), -L, l, r);
printf("%d\n", rs);
}
L
序列去重、计数问题。
难点:从输入的SQL语句中提取出关键字,然后筛选出目标列。序列计数操作。
可以直接用 map
直接用 map 166 ms
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 1010;
int n, m;
string s[N], q;
int a[N][N];
vector<int> v;
unordered_map<string,int> mp;
vector<vector<int>> b;
map<vector<int>, int> mp2;
vector<int> rs;
int main(){
scanf("%d%d", &n, &m);
rep(i,1,m) cin >> s[i], mp[s[i]] = i;
rep(i,1,n) rep(j,1,m) scanf("%d", &a[i][j]);
getline(cin, q); // 读取空行
getline(cin, q); // 读取 SQL
q = q.substr(36);
q.pop_back(); // 将最后的分号去掉
int i = 0;
while(i < q.size()) {
int j = q.find(',', i); // 获得下一个 ',' 的位置
if(j == -1) j = q.size(); // 如果没有,直接定位到最后
string t = q.substr(i, j - i); // 获得子串 term
v.push_back(mp[t]);
i = j + 1;
}
rep(i,1,n) {
vector<int> t;
for(auto &x : v) {
t.push_back(a[i][x]);
}
mp2[t] ++;
}
printf("%d\n",(int) mp2.size());
for(auto [k, v] : mp2) {
printf("%d ", v);
}
puts("");
return 0;
}
对 vector
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=int(j);i<=int(k);i++)
#define per(i,j,k) for(int i=int(j);i>=int(k);i--)
typedef long long ll;
const int N = 1010;
int n, m;
string s[N], q;
int a[N][N];
vector<int> v;
unordered_map<string,int> mp;
vector<vector<int>> b;
vector<int> rs;
int main(){
scanf("%d%d", &n, &m);
rep(i,1,m) cin >> s[i], mp[s[i]] = i;
rep(i,1,n) rep(j,1,m) scanf("%d", &a[i][j]);
getline(cin, q);
getline(cin, q);
q = q.substr(36);
q.pop_back();
int i = 0;
while(i < q.size()) {
int j = q.find(',', i);
if(j == -1) j = q.size();
string t = q.substr(i, j - i);
v.push_back(mp[t]);
i = j + 1;
}
rep(i,1,n) {
vector<int> t;
for(auto &x : v) {
t.push_back(a[i][x]);
}
b.push_back(t);
}
// 排序
sort(b.begin(), b.end());
for(int i = 0; i < b.size(); i++){
if(i == 0 || b[i] != b[i-1]) { // 与前面的不一样,则新增一个 1
rs.push_back(1);
} else { // 一样则 ++
rs[rs.size() - 1] ++;
}
}
cout << rs.size() << endl;
for(auto &x : rs) printf("%d ", x);
puts("");
return 0;
}