A、九峰与签到题
题意:
给定
n
n
n道题,以及所有的
m
m
m条提交记录,一道题是签到题当且仅当在任意一个时间段内其通过率都不小于
50
%
50\%
50%,输出所有的签到题。当一道题没有提交记录时,一定不是签到题。
数据范围:
1
≤
n
≤
20
,
1
≤
m
≤
1
0
5
1\leq n\leq 20, 1\leq m\leq 10^5
1≤n≤20,1≤m≤105
题解:
暴力统计即可,注意当存在一个时刻一道题的通过率低于
50
%
50\%
50%时,其就不可为签到题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define sz(x) (int)x.size()
typedef long long ll;
const int N = 30, M = 1e5 + 10;
int all[N];
int cnt[N];
int n, m;
char op[100];
int num;
int ans[N], g;
struct Node {
int p, v;
};
vector<Node> a[N];
void solve() {
scanf("%d%d", &m, &n);
for(int i = 1; i <= m; ++i) {
scanf("%d%s", &num, op);
a[num].push_back({i, *op == 'A'});
}
for(int i = 1; i <= n; ++i) {
if(sz(a[i]) == 0) continue;
int sum = 0, f = 1;
for(int j = 0; j < sz(a[i]); ++j) {
sum += a[i][j].v;
if(sum * 2 < (j + 1)) {
f = 0;
break;
}
}
if(f) ans[++g] = i;
}
if(g == 0) puts("-1");
else for(int i = 1; i <= g; ++i) printf("%d%c", ans[i], " \n"[i == g]);
}
int main()
{
int T = 1;
//scanf("%d", &T);
for(int i = 1; i <= T; ++i) solve();
return 0;
}
B、武辰延的字符串
题意:
给定串
s
s
s和
t
t
t,找出满足
s
s
s的一个前缀
s
i
s_i
si和另一个前缀
s
j
s_j
sj,有
s
i
+
s
j
=
t
i
+
j
s_i+s_j=t_{i+j}
si+sj=ti+j。
其中
i
i
i可以等于
j
j
j,前缀
(
s
i
,
s
j
)
(s_i,s_j)
(si,sj)和
(
s
i
′
,
s
j
′
)
(s_{i'},s_{j'})
(si′,sj′)中,
i
≠
i
′
,
j
≠
j
′
i\neq i',j\neq j'
i=i′,j=j′,输出满足条件的前缀对数。
数据范围:
1
≤
∣
s
∣
,
∣
t
∣
≤
1
0
5
1\leq |s|,|t|\leq 10^5
1≤∣s∣,∣t∣≤105
题解:
解法一:
考虑由于需要满足一个前缀
s
i
=
s_i=
si=前缀
t
i
t_i
ti,所以考虑二分前缀
s
j
s_j
sj和
t
[
i
+
1
,
i
+
j
]
t_{[i+1,i+j]}
t[i+1,i+j],枚举最长的
j
j
j,那么前缀
s
i
s_i
si可以构成的方案数就是
j
j
j。
解法二:
exkmp待补
代码:
解法一:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
char s[N], t[N];
int n, m;
const int P = 131;
ull spre[N], tpre[N], p[N];
bool check(ull A[], int l, int r, ull B[], int L, int R) {
int len = r - l + 1;
return A[r] - A[l - 1] * p[len] == B[R] - B[L - 1] * p[len];
}
int main()
{
scanf("%s", s + 1); n = strlen(s + 1);
scanf("%s", t + 1); m = strlen(t + 1);
int k = max(n, m);
p[0] = 1;
for(int i = 1; i <= k; ++i) p[i] = p[i - 1] * P;
for(int i = 1; i <= n; ++i) spre[i] = spre[i - 1] * P + s[i];
for(int i = 1; i <= m; ++i) tpre[i] = tpre[i - 1] * P + t[i];
ll res = 0;
for(int i = 1; i <= min(n, m); ++i) {
if(s[i] != t[i]) break;
int l = 0, r = m - i;
while(l < r) {
int len = l + r + 1 >> 1;
if(check(spre, 1, len, tpre, i + 1, i + len)) l = len;
else r = len - 1;
}
res += r;
}
printf("%lld\n", res);
return 0;
}
C、九峰与CFOP
大模拟直接跳过,这辈子也不会补的。
D、温澈滢的狗狗
题意:
给定
n
n
n只狗以及每只狗的颜色
c
i
c_i
ci,如果编号为
i
i
i的狗和编号为
j
j
j的狗的颜色不同,那么他们有亲密关系,且亲密度为:
∣
i
−
j
∣
|i-j|
∣i−j∣
把所有的亲密关系按照第一关键字为亲密度大小,第二关键字为编号较小的狗狗,第三关键字为编号较大的狗狗,均按照升序排序,问第
k
k
k对亲密关系是哪两只狗狗,输出两只狗狗的编号,小的在前,大的在后。
数据范围:
1
≤
n
≤
1
0
5
,
1
≤
k
≤
n
×
(
n
−
1
)
2
,
1
≤
c
i
≤
n
1\leq n\leq 10^5, 1\leq k\leq \frac{n\times(n-1)}{2},1\leq c_i\leq n
1≤n≤105,1≤k≤2n×(n−1),1≤ci≤n
题解:
考虑二分第
k
k
k对亲密关系的亲密度
x
x
x。
那么亲密度为
x
x
x的亲密关系=任意编号差绝对值不大于
x
x
x的狗狗关系减去所有同色之间编号差绝对值不大于
x
x
x的狗狗关系。
这样再可以最多
O
(
n
)
O(n)
O(n)枚举到第
k
k
k对关系即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define sz(x) (int)x.size()
typedef long long ll;
const int N = 1e5 + 10;
int c[N], n;
ll k;
vector<int> g[N];
ll count(int mid) {
ll sum = 0;
for(int i = 1; i <= n; ++i) sum += min(mid, n - i);
for(int i = 1; i <= n; ++i)
if(sz(g[i]))
for(int y = 0, x = 0; y < sz(g[i]); ++y) {
while(g[i][y] - g[i][x] > mid) ++x;
sum -= (y - x);
}
return sum;
}
int main()
{
scanf("%d%lld", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &c[i]), g[c[i]].emplace_back(i);
int l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
if(count(mid) >= k) r = mid;
else l = mid + 1;
}
if(l == n) puts("-1");
else {
k -= count(l - 1);
for(int i = 1; i + l <= n; ++i)
if(c[i] != c[i + l])
if(--k == 0) {
printf("%d %d\n", i, i + l);
break;
}
}
return 0;
}
E、九峰与子序列
题意:
给定一个字符串
s
s
s和
s
s
s的
n
n
n个字符串序列,第
i
i
i个为
t
i
t_i
ti,问有多少种选择使得子序列可以组成为
s
s
s。
数据范围:
1
≤
∣
s
∣
≤
5
×
1
0
6
,
∑
i
=
1
n
∣
t
i
∣
≤
5
×
1
0
6
1\leq |s|\leq 5\times 10^6,\sum_{i=1}^n |t_i|\leq 5\times10^6
1≤∣s∣≤5×106,∑i=1n∣ti∣≤5×106
题解:
题目中已然提示了使用字符串hash,可我一贯没有读题~
本题用字符串hash来找当前的
t
i
t_i
ti可以和
s
s
s匹配的位置。
这里可以将可以匹配的位置看做为
01
01
01背包问题中的物品装背包。
总体复杂度为
2
e
8
2e8
2e8,常数较小加上跑的很猛的评测机可以通过。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int M = 5e6 + 10;
const int P = 131;
int n;
ull h1[M], p[M];
char s[M];
ll f[M];
ull get(int l, int r) {
return h1[r] - h1[l - 1] * p[r - l + 1];
}
int main()
{
scanf("%d", &n);
scanf("%s", s + 1);
int len = strlen(s + 1);
p[0] = 1;
for(int i = 1; i <= len; ++i) {
h1[i] = h1[i - 1] * P + s[i];
p[i] = p[i - 1] * P;
}
f[0] = 1;
for(int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
int lens = strlen(s + 1);
ull hv = 0;
for(int j = 1; j <= lens; ++j) hv = hv * P + s[j];
for(int j = len; j >= lens; --j)
if(hv == get(j - lens + 1, j))
f[j] += f[j - lens];
}
printf("%lld\n", f[len]);
return 0;
}
F、魏迟燕的自走棋
题意:
共
n
n
n个人,
m
m
m件装备,第
i
i
i件装备的战力值为
w
i
w_i
wi,第
i
i
i件装备可以给指定
k
i
k_i
ki个人中的一个,分别为
p
1
,
p
2
,
.
.
.
,
p
k
i
p_1,p_2,...,p_{k_i}
p1,p2,...,pki
求所有人分到装备的战力值和的最大值。
数据范围:
1
≤
n
,
m
≤
1
0
5
,
1
≤
w
i
≤
1
0
9
,
1
≤
p
i
≤
n
,
1
≤
k
i
≤
2
1\leq n,m\leq 10^5,1\leq w_i\leq 10^9,1\leq p_i\leq n,1\leq k_i\leq 2
1≤n,m≤105,1≤wi≤109,1≤pi≤n,1≤ki≤2
题解:
考虑贪心拿取,有点类似最小生成树的生成过程。
先按装备战力值从大到小排序然后贪心拿取,用并查集来维护是否该装备还可拿取。
具体做法为:
一个装备可以被拿是看
x
,
y
x,y
x,y两个人中是否至少有一个可以拿。拿完这个装备后,
x
,
y
x,y
x,y这两个人最多可以再拿一个与
x
x
x或
y
y
y有关的装备,否则没人可以拿。因此一个做法就是将
x
x
x和
y
y
y合并到一个集合中,如果两者之前至少有一个没有拿装备,那这个集合可以拿一个与之有关的装备,否则就不行。特别的,当一个装备只有一个人可以拿且这个人所在集合还可以拿时,其拿下这个装备。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Node {
int x, y, z;
bool operator < (const Node &A) const {
return z > A.z;
}
}a[N];
int n, m;
int p[N], f[N];
int find(int x) {
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) p[i] = i, f[i] = 0;
for(int i = 1; i <= m; ++i) {
int cnt; scanf("%d", &cnt);
if(cnt == 1) scanf("%d%d", &a[i].x, &a[i].z), a[i].y = a[i].x;
else scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
}
sort(a + 1, a + m + 1);
long long res = 0;
for(int i = 1; i <= m; ++i) {
int x = find(a[i].x), y = find(a[i].y), v = a[i].z;
if(x != y && (f[x] == 0 || f[y] == 0)) {
res += v;
p[x] = y;
f[y] = (f[x] || f[y]);
}
else if(f[x] == 0){
res += v;
f[x] = 1;
}
}
printf("%lld\n", res);
return 0;
}
G、九峰与蛇形填数
待补
H、吴楚月的表达式
题意:
给定一棵
n
n
n点
n
−
1
n-1
n−1条边的树,每个点有一个点权
w
i
w_i
wi,每条边有一个操作符,
1
1
1号点为根。从根到每个点会产生一个表达式,请你按照加减乘除的优先级正确计算这个表达式的值。由于结果可能很大,所以答案对
1
e
9
+
7
1e9+7
1e9+7取模。
数据范围:
1
≤
n
≤
1
0
5
,
1
≤
w
i
≤
1
0
9
,
1
≤
f
a
i
≤
i
,
o
p
i
∈
{
+
,
−
,
∗
,
/
}
1\leq n\leq 10^5, 1\leq w_i\leq 10^9, 1\leq fa_i\leq i, op_i\in \{+,-,*,/\}
1≤n≤105,1≤wi≤109,1≤fai≤i,opi∈{+,−,∗,/}
题解:
为了方便处理,我们将所有的减法预处理转换为加法,除法预处理转换为乘法。
那么我们只需要去考虑加号和乘号的优先级即可。
1
1
1号点不进行计算,表达式的值即为
w
1
w_1
w1
- 考虑当前是乘法,那么优先级已然是最高,所以只要看当前数的个数,如果两个,乘到第二个上,否则乘到第一个上即可
- 考虑当前是加法,那么后面可能会出现乘法,所以看当前数的个数,如果一个,那么将该数放置到第二个数即可,否则先将两个数相加合并,再把当前数放置到第二个数。
最后只需要第一个数和第二个数(如果第二个数存在的话)相加即每个点的答案。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, M = N;
const int mod = 1e9 + 7;
int h[N], w[M], e[M], ne[M], idx;
int a[N], n;
int fa[N];
char s[N];
int res[N];
struct Ans {
int a, b;
Ans() {b = -1;}
}ans[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int qp(int a, int b) {
int ans = 1;
while(b) {
if(b & 1) ans = (ll)ans * a % mod;
a = (ll)a * a % mod;
b >>= 1;
}
return ans;
}
int id(char ch) {
if(ch == '+') return 0;
else if(ch == '-') return 1;
else if(ch == '*') return 2;
return 3;
}
int cal(int a, int b, int c) {
int res;
if(c == 0) res = (a + b) % mod;
else if(c == 1) res = (ll)a * b % mod;
return res;
}
void dfs(int u) {
for(int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
ans[v] = ans[u];
//multiple
if(w[i] == 1) {
if(ans[u].b == -1) ans[v].a = cal(ans[v].a, a[v], w[i]);
else ans[v].b = cal(ans[v].b, a[v], w[i]);
}
//addition
else {
if(ans[u].b != -1) ans[v].a = cal(ans[v].a, ans[v].b, 0);
ans[v].b = a[v];
}
dfs(v);
}
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for(int i = 2; i <= n; ++i) scanf("%d", &fa[i]);
scanf("%s", s + 2);
memset(h, -1, n + 1 << 2);
for(int i = 2; i <= n; ++i) {
int sig = id(s[i]);
if(sig == 3) a[i] = qp(a[i], mod - 2);
else if(sig == 1) a[i] = ((-a[i]) % mod + mod) % mod;
add(fa[i], i, sig >> 1);
}
ans[1].a = a[1];
dfs(1);
for(int i = 1; i <= n; ++i) {
int c = ans[i].a;
if(~ans[i].b) c = (c + ans[i].b) % mod;
printf("%d%c", c, " \n"[i == n]);
}
return 0;
}
I、九峰与分割序列
待补
J、邬澄瑶的公约数
题意:
给定
n
n
n个数以及每个数的幂,问这些数的幂的最大公约数是多少。答案对
1
e
9
+
7
1e9+7
1e9+7取模。特别地,
g
c
d
(
x
)
=
x
gcd(x)=x
gcd(x)=x
数据范围:
1
≤
n
,
x
i
,
p
i
≤
10000
1\leq n,x_i,p_i\leq 10000
1≤n,xi,pi≤10000
题解:
分解每个数的质因数,最终的最大公约数中的质因数最大幂为给定
n
n
n个数的质因数最小幂。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 10;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int pri[N], cnt;
bool st[N];
int n;
int act[N];
struct Node {
int x, p;
bool operator < (const Node &A) const {
return x < A.x;
}
}a[N];
void xs(int n) {
st[0] = st[1] = true;
for(int i = 2; i <= n; ++i) {
if(!st[i]) pri[++cnt] = i;
for(int j = 1; j <= cnt && i <= n / pri[j]; ++j) {
st[i * pri[j]] = true;
if(i % pri[j] == 0) break;
}
}
}
int qp(int a, int b) {
int ans = 1;
while(b) {
if(b & 1) ans = (ll)ans * a % mod;
a = (ll)a * a % mod;
b >>= 1;
}
return ans;
}
int main()
{
ll res = 1;
scanf("%d", &n);
int mx = 0;
for(int i = 1; i <= n; ++i) scanf("%d", &a[i].x), mx = max(mx, a[i].x);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i].p);
xs(mx);
sort(a + 1, a + n + 1);
memset(act, 0x3f, sizeof act);
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= cnt; j++) {
if(act[pri[j]] == 0) continue;
if(a[i].x % pri[j]) act[pri[j]] = 0;
else {
int c = 0;
int xx = a[i].x;
while(xx % pri[j] == 0) ++c, xx /= pri[j];
act[pri[j]] = min(act[pri[j]], c * a[i].p);
}
}
}
for(int i = 1; i <= cnt; ++i) res = res * qp(pri[i], act[pri[i]]) % mod;
printf("%lld\n", res);
return 0;
}