“卓見杯”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;
}
}
}