“卓见杯”2020年河南省CCPC 虚拟赛补题总结
班委竞选
知识点:结构体排序
广告投放
知识点:dp,数论分块
思路:
定义\(f(i,j)\)为考虑前\(i\)个节目,观众为\(j\)的最大收益
转移方程为\(f(i,j/d[i])=max(f(i-1,j/d[i]),f(i-1,j)+j*p[i])\)
此时时间复杂度为\(O(nm)\)的
利用数论分块优化\(dp\)
\(⌊⌊n/i⌋ /j⌋ = ⌊n/(i · j)⌋\)
$⌊n/i⌋ $的取值只有 \(O(√n)\) 种(数论分块、整数分块)
所以先把所有的取值取出来,在进行\(dp\),时间复杂度\(O(n√m)\)
第一维可以利用滚动数组,改成\(0/1\),或者开2个\(dp\)数组来转移
View Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
#define int long long
int n, m;
int p[N], d[N];
int f[N][2];
int g[N];
int val[N];
int cnt;
int vis[N];
signed main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> p[i];
for (int i = 1; i <= n; i++) cin >> d[i];
val[++cnt] = m;
vis[m] = 1;
for (int i = m; i >= 1; i--) {
if (vis[i]) {
for (int j = 1; j <= i; j++) {
vis[i / j] = 1;
}
val[++cnt] = i;
}
}
sort(val + 1, val + cnt + 1);
cnt = unique(val + 1, val + 1 + cnt) - val - 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= cnt; j++) {
f[val[j] / d[i]][0] = max(f[val[j] / d[i]][0], f[val[j]][1] + val[j] * p[i]);
}
for (int j = 1; j <= cnt; j++) {
f[val[j]][1] = f[val[j]][0];
}
}
int res = 0;
for (int i = 0; i <= m; i++) {
res = max(res, f[i][0]);
}
cout << res << endl;
}
我得重新集结部队
知识点:大模拟
发通知
知识点:差分,离散化
思路:
利用\(map\)或者手动离散化,再进行差分,求一遍前缀和,看满足题意的时间点中的异或和最大值
View Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
struct node {
int l, r;
int w;
} a[N];
int n, k;
int res;
map<int, int> x;
map<int, int> y;
bool cmp(node a, node b) {
if (a.l == b.l) return a.r < b.r;
return a.l < b.l;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i].l >> a[i].r >> a[i].w;
}
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; i++) {
int l = a[i].l, r = a[i].r;
int w = a[i].w;
x[l] += 1, x[r + 1] -= 1;
y[l] ^= w, y[r + 1] ^= w;
}
bool is_first = 1;
int now = 0;
for (auto it : x) {
if (is_first) {
is_first = 0;
now = it.second;
continue;
} else {
now += it.second;
x[it.first] = now;
}
}
is_first = 1;
now = 0;
for (auto it : y) {
if (is_first) {
is_first = 0;
now = it.second;
continue;
} else {
now = now ^ it.second;
y[it.first] = now;
}
}
int res = -1;
for (auto it : x) {
if (it.second >= k) {
res = max(res, y[it.first]);
}
}
cout << res << endl;
}
旅游胜地
知识点:2-SAT,二分
思路:
二分距离,然后看当前这种距离下的建图,跑2-SAT,然后就看\(x\)和\(¬x\)是否在一个强连通分量里
建边时,就看当前这对点选\(a\)或\(b\)值,是否满足题意,例如,\(|a_x-b_y|>mid\) ,那么就是\(¬x∨¬y\),也就是x向y+n连边,y向x+n连边,其余情况类似
还有树形\(dp\)的做法,不过没有理解,用的\(2-SAT\)的做法
View Code
#include <bits/stdc++.h>
using namespace std;
const int N = 8e5 + 10;
int n, m;
int a[N], b[N];
int h[N], e[N], ne[N], idx;
int dfn[N], low[N], timetamp;
int sta[N], top;
int in_sta[N], id[N], scc_cnt;
int u[N], v[N];
void add_edge(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
void init() {
memset(h,-1,sizeof(h));
idx=0;
top = 0, scc_cnt = 0, timetamp = 0;
for (int i = 0; i <= 2 * n + 2; i++) {
dfn[i] = low[i] = sta[i] = in_sta[i] = id[i] = 0;
}
}
void tarjan(int u) {
dfn[u] = low[u] = ++timetamp;
sta[++top] = u;
in_sta[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!dfn[j]) {
tarjan(j);
low[u] = min(low[u], low[j]);
} else if (in_sta[j]) {
low[u] = min(low[u], dfn[j]);
}
}
if (dfn[u] == low[u]) {
scc_cnt++;
int y;
do {
y = sta[top--];
in_sta[y] = 0;
id[y] = scc_cnt;
} while (y != u);
}
}
bool check(int s) {
init();
for (int i = 1; i <= m; i++) {
int x = u[i], y = v[i];
if (abs(a[x] - a[y]) > s) {
add_edge(x, y + n);
add_edge(y, x + n);
}
if (abs(a[x] - b[y]) > s) {
add_edge(x, y);
add_edge(y + n, x + n);
}
if (abs(b[x] - a[y]) > s) {
add_edge(x + n, y + n);
add_edge(y, x);
}
if (abs(b[x] - b[y]) > s) {
add_edge(x + n, y);
add_edge(y + n, x);
}
}
for (int i = 1; i <= 2 * n; i++) {
if (!dfn[i]) {
tarjan(i);
}
}
for (int i = 1; i <= n; i++) {
if (id[i] == id[i + n]) {
return 0;
}
}
return 1;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
}
for (int i = 1; i <= m; i++) {
scanf("%d%d", &u[i], &v[i]);
}
int l = 0, r = 2e9;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
printf("%d\n", l);
}
太阳轰炸
知识点:二项分布,概率,组合数
思路:
炸到虚空的概率为\(p=min(1,\frac{(R_1+r)^2}{(R_2)^2})\),算出至少要炸\(cnt\)次
那么期望就是\(\sum_{i=cnt}^{n} C_n^i p^i (1-p)^{n-i}\qquad\)
View Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int mod = 1e9 + 7;
const int N = 6e6 + 10;
int fact[N], infact[N];
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
void init() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i++) {
fact[i] = i * fact[i - 1] % mod;
}
infact[N - 1] = qmi(fact[N - 1], mod - 2) % mod;
for (int i = N - 2; i >= 1; i--) {
infact[i] = infact[i + 1] * (i + 1) % mod;
}
}
int C(int n, int m) {
if (m > n) return 0;
return fact[n] * infact[n - m] % mod * infact[m] % mod;
}
signed main() {
init();
int n, R1, R2, r, atk, h;
cin >> n >> R1 >> R2 >> r >> atk >> h;
int nR = R1 + r;
int a = nR * nR; //交面积
int b = R2 * R2; //太阳面积
int A = b - a;
int B = b;
int cnt = (h + atk - 1) / atk;
if (cnt > n) {
cout << 0 << endl;
return 0;
}
if (R1 + r >= R2) {
puts("1");
return 0;
}
a = a % mod;
b = b % mod;
A = (b - a + mod) % mod;
int res = 0;
int y = qmi(2, n) % mod;
y = qmi(2, mod - 2) % mod;
int down = qmi(b, n) % mod;
down = qmi(down, mod - 2) % mod;
for (int i = cnt; i <= n; i++) {
int x = C(n, i) % mod;
int up = qmi(a, i) % mod * qmi(A, n - i) % mod;
res = (res % mod + x * up % mod * down % mod) % mod;
res = res % mod;
}
res = res % mod;
cout << res << endl;
}
二进制与、平方和
知识点:线段树
思路:
线段树维护每个数二进制下的每一位,由于是\(AND\)操作,所以对于每一位上,若是\(1\),则一直不用改,可以发现需要修改的情况只有当前位置是\(1\),\(x\)的当前位置为\(0\),这样操作后,就会变成\(0\),但是这个题维护每一位的话,不方便在pushdown的时候,在修改每一位的值的情况下同时算出区间平方和,所以区间修改,懒标记不好维护(可能有懒标记的做法,我没想到),这样的话,可以递归维护每一位和平方和,在修改操作时,对于修改二进制下的每一位的值时,就看每一位上的\(0/1\)来修改,平方和在修改完儿子结点后,回溯回来pushup更新父亲结点的区间平方和的值,其余操作就和基础线段树一样
时间复杂度详细分析看题解
View Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 3e5 + 10;
const int mod = 998244353;
struct node {
int l, r;
int a[25];
int sum;
} tr[N * 4];
int n, m;
int a[N];
void push_up(int u, int l, int r) {
for (int i = 0; i < 25; i++) {
tr[u].a[i] = tr[l].a[i] + tr[r].a[i];
}
tr[u].sum = (tr[l].sum + tr[r].sum) % mod;
}
void push_up(int u) { push_up(u, u << 1, u << 1 | 1); }
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) {
for (int i = 0; i < 25; i++) {
if ((a[l] >> i) & 1) {
tr[u].a[i] = 1;
}
}
tr[u].sum = a[l] * a[l] % mod;
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
push_up(u);
}
void modify(int u, int l, int r, int x) {
if (tr[u].l == tr[u].r) {
int res = 0;
for (int i = 0; i < 25; i++) {
if ((tr[u].a[i]) && (((x >> i) & 1) == 0)) {
tr[u].a[i] = 0;
}
}
for (int i = 0; i < 25; i++) {
if (tr[u].a[i]) {
res = res | 1 << i;
}
}
tr[u].sum = res * res % mod;
return;
}
if (l <= tr[u].l && tr[u].r <= r) {
bool f = 1;
for (int i = 0; i < 25; i++) {
if ((tr[u].a[i]) && ((x >> i) & 1) == 0) {
f = 0;
break;
}
}
if (f) return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, x);
if (r > mid) modify(u << 1 | 1, l, r, x);
push_up(u);
}
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].sum;
}
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if (l <= mid) res = (res + query(u << 1, l, r)) % mod;
if (r > mid) res = (res + query(u << 1 | 1, l, r)) % mod;
return res;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
cin >> m;
while (m--) {
int op, l, r, x;
cin >> op;
if (op == 2) {
cin >> l >> r;
cout << query(1, l, r) << endl;
} else {
cin >> l >> r >> x;
modify(1, l, r, x);
}
}
return 0;
}
子串翻转回文串
知识点:字符串哈希
思路:
若字符串本身就是回文串就不用判,对于不是回文串的,如果翻转某个区间就是回文串,说明左右两端有部分相同的字符,对于相同的字符,肯定是不用翻转的,是无效操作,所以去找第一次左右对称位置不同字符的位置,对于找到的字符串,翻转情况只有两种情况,要么是固定左端点,枚举右端点去翻转,或者固定右端点,枚举左端点去翻转,翻转后看字符串是否回文,就用字符串哈希来判断
View Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned long long
#define endl "\n"
const int N = 5e5 + 10, P = 131;
const int mod = 1e9 + 7;
int n;
int h[N], p[N], uh[N];
char s[N];
int get1(int l, int r)
{
return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;
}
int get2(int l, int r)
{
return (uh[l] - uh[r + 1] * p[r - l + 1] % mod + mod) % mod;
}
bool check(int l, int r, int len)
{
int x = h[len], y = uh[1];
int dex = (h[len] - get1(l, r) * p[len - r] % mod + get2(l, r) * p[len - r] % mod + mod) % mod;
int dey = (uh[1] - get2(l, r) * p[l - 1] % mod + get1(l, r) * p[l - 1] % mod + mod) % mod;
return dex == dey;
}
signed main()
{
int T;
cin >> T;
while (T--)
{
scanf("%s", s + 1);
bool flag = 0;
int len = strlen(s + 1);
for (int i = 1; i <= len / 2; i++)
{
if (s[i] != s[len - i + 1])
{
flag = 0;
break;
}
}
if (flag)
{
cout << "Yes" << endl;
}
else
{
p[0] = 1;
h[0] = 0;
for (int i = 1; i <= len; i++)
{
h[i] = (h[i - 1] * P % mod + s[i]) % mod;
p[i] = p[i - 1] * P % mod;
}
uh[len + 1] = 0;
for (int i = len; i >= 1; i--)
{
uh[i] = (uh[i + 1] * P % mod + s[i]) % mod;
}
int pos = 0;
for (int i = 1; i <= len; i++)
{
if (s[i] != s[len - i + 1])
{
pos = i;
break;
}
}
for (int i = pos; i <= len - pos + 1; i++)
{
int l = pos, r = i;
int ll = i, rr = len - pos + 1;
if (check(l, r, len) || check(ll, rr, len))
{
flag = 1;
break;
}
}
if (flag)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
}

浙公网安备 33010602011771号