2021.11.3模擬賽 賽後總結
A. 質數統計(prime)
Description
給定 \(n\) 個正整數 \(a_1,a_2\dots a_n\),定義 \(Cnt(p)\) 表示這 \(n\) 個數中能被質數 \(p\) 整除的數的個數,不是質數的 \(Cnt()\) 為 \(0\)。
有 \(m\) 組詢問,每組詢問輸入 \(l,r\),求 \(\sum_{i=l}^rCnt(i)\)
\(1\le n,m \le 10^6,1\le a_i \le 10^7,1\le l \le r \le 10^7\)
Solution
顯然先預處理出 \(cnt\) 陣列,做個字首和,\(O(1)\) 查詢。
可以用線性篩,同時求出每個數的最小質因子 \(mindiv\)
也可以不處理 \(mindiv\),就仿照質因數分解的過程也可以過
題上說要對 \(10^9+7\) 取模,但事實證明並不用。
Code
#include <bits/stdc++.h> #define ll long long using namespace std; template <typename T> void read(T &x) { x = 0; char c = getchar(); while(!isdigit(c)) c = getchar(); while(isdigit(c)) x = x * 10 + c - '0', c = getchar(); return; } template <typename T> void write(T x) { if(x > 9) write(x / 10); putchar(x % 10 + '0'); return; } const int N = 1e6 + 5; const int MAXN = 1e7 + 5; const int P = 1e9 + 7; int n, m, a[N], maxn; bool flag[MAXN]; int p[N], tot, mindiv[MAXN]; void Euler(int n) { flag[1] = 1; for(int i = 2; i <= n; i++) { if(!flag[i]) p[++tot] = i, mindiv[i] = i; for(int j = 1; j <= tot && i * p[j] <= n; j++) { flag[i * p[j]] = 1; mindiv[i * p[j]] = p[j]; if(i % p[j] == 0) break; } } return; } ll cnt[MAXN]; void Divide(int x) { while(x > 1) { cnt[mindiv[x]]++; int d = mindiv[x]; while(x % d == 0) x /= d; } return; } int main() { read(n), read(m); for(int i = 1; i <= n; i++) read(a[i]), maxn = max(maxn, a[i]); Euler(maxn); for(int i = 1; i <= n; i++) Divide(a[i]); for(int i = 1; i < MAXN; i++) cnt[i] += cnt[i - 1]; while(m--) { int l, r; read(l), read(r); write(cnt[r] - cnt[l - 1]); putchar('\n'); } return 0; }
B. 城市規劃(planning)
Description
給定 \(n\) 棟樓的高度 \(h_i\),定義這些樓的不美觀度為 \(max_{1\le i\le n}\{|h_i-h_{i+1}|\}\)。
現在你可改變任意 \(k\) 棟樓的高度,使得不美觀度最小。
輸出最小值為多少。
\(1\le k \le n \le 2000,1\le h_i \le 2\times 10^9\)
Solution
觀察到是最大值最小,不難想到二分答案。
對於二分的答案 \(x\),考慮如何判斷是否可行
設 \(f_i\) 表示將前 \(i\) 棟樓改成合法的最少需要改幾棟樓的高度
初值為 \(i-1\)
\(f_i=min(f_i,f_j+i-j-1)\)
最後將 \(n\) 棟樓列舉一遍,判斷是否存在 \(f_i+n-i \le k\)。
注意樓比較高,要開 long long
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
template <typename T>
void read(T &x)
{
x = 0; char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return;
}
template <typename T>
void write(T x)
{
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
const int N = 2010;
int n, k, a[N], ans;
int f[N];
bool chk(int x)
{
for(int i = 1; i <= n; i++)
{
f[i] = i - 1;
for(int j = 1; j < i; j++)
if(abs(a[i] - a[j]) <= 1ll * x * (i - j))
f[i] = min(f[i], f[j] + i - j - 1);
}
for(int i = 1; i <= n; i++)
if(f[i] + n - i <= k) return true;
return false;
}
signed main()
{
read(n), read(k);
int l = 0, r = 0;
for(int i = 1; i <= n; i++)
read(a[i]), r = max(r, abs(a[i] - a[i - 1]));
while(l <= r)
{
int mid = (l + r) >> 1;
if(chk(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
write(ans), putchar('\n');
return 0;
}
C. 花環(garland)
Description
給定 \(n\) 個點的環,劃分為 \(m\) 段,最大化和。
\(1\le n \le 3 \times 10^5,1\le m \le 10^5\)
Solution
首先注意到一個性質:如果有連續一段權值都為正的點,那麼如果選擇了一個,肯定選完一整段(可能分成幾段來選)。
如果這樣的段的個數 \(\le m\),那麼答案即為這些段的和。
否則,要減少一些段,有兩種方法可以減少一段:
- 少選一整段全為正的
- 選擇一段全為負的,將兩段正的連起來
所以一整段負的也是要麼不選,要麼全選
那麼我們可以將這樣連續的正的和負的縮成一個點,就變成了一個正負相間的環
考慮如何減到剩 \(m\) 段。
先把負的權值改為正的,然後每次選出一段就把和減掉,答案的初值為所有正段的和。
不難發現,如果選出第 \(i\) 段,那麼兩邊的段一定不會再被選,因為如果第 \(i\) 段原來是正的,那麼已經減少了一段,再選兩邊的會使答案更劣,如果第 \(i\) 段原來是負的,那麼選兩邊也會使答案更劣。
但是我們不能只貪心的去選當前最優的,因為有可能再選了其他段後,另一種方案更優,所以要進行反悔貪心。
具體請見 Luogu P3620 資料備份
Code
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void read(T &x)
{
x = 0; int f = 1; char c = getchar();
while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
x *= f;
return;
}
template <typename T>
void write(T x)
{
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
const int N = 6e5 + 5;
typedef pair<int, int> P;
int n, m, a[N], ans;
int val[N], cnt;
int pre[N], nxt[N];
bool vis[N];
priority_queue <P, vector<P>, greater<P> > que;
void del(int x)
{
vis[x] = 1;
nxt[pre[x]] = nxt[x];
pre[nxt[x]] = pre[x];
return;
}
int main()
{
read(n), read(m);
for(int i = 1, lst = 0; i <= n; lst = a[i], i++)
{
read(a[i]);
if(a[i] * lst > 0) val[cnt] += a[i];
else val[++cnt] = a[i];
}
if(val[1] * val[cnt] > 0 && cnt > 1) val[1] += val[cnt], cnt--;
n = cnt, cnt = 0;
for(int i = 1; i <= n; i++)
if(val[i] > 0) ans += val[i], cnt++;
if(cnt <= m)
{
write(ans);
return 0;
}
for(int i = 1; i <= n; i++)
{
pre[i] = i - 1, nxt[i] = i + 1;
if(val[i] < 0) val[i] = -val[i];
que.push(P(val[i], i));
}
pre[1] = n, nxt[n] = 1;
while(cnt > m)
{
cnt--;
while(vis[que.top().second]) que.pop();
P p = que.top();
que.pop();
ans -= p.first;
int id = p.second;
val[id] = val[pre[id]] + val[nxt[id]] - val[id];
del(pre[id]), del(nxt[id]);
que.push(P(val[id], id));
}
write(ans);
return 0;
}
D. 密碼門(cipher)
Description
給定 \(n\) 個操作符和引數,操作符包括按位與、按位或、按位異或,求一個數通過這些操作後的值,多組詢問,帶修。
修改某個操作的操作符和引數。
\(1\le n,m \le 2\times 10^5,1\le x \le 1000\)
Solution
用線段樹維護二進位制每一位上一開始是 \(0\) 或 \(1\),最後結果是多少。
因為數最大隻有 \(1000\),二進位制也就 \(10\) 位,因此對每一位建個線段樹還是可以的,不過貌似有壓在一起的做法?不太會
pushup 就是將在左子樹裡求出來的結果放在右子樹裡再求
Code
#include <bits/stdc++.h>
#define ls (rt << 1)
#define rs (rt << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
int n, m, a[N];
int op[N], x[N];
int t[10][N << 2][2]; // & | ^
int get_op(char c)
{
if(c == 'A') return 0;
if(c == 'O') return 1;
if(c == 'X') return 2;
}
void pushup(int b, int rt)
{
t[b][rt][0] = t[b][rs][t[b][ls][0]];
t[b][rt][1] = t[b][rs][t[b][ls][1]];
}
void build(int b, int l, int r, int rt)
{
if(l == r)
{
if(op[l] == 0) t[b][rt][0] = 0, t[b][rt][1] = a[l];
if(op[l] == 1) t[b][rt][0] = a[l], t[b][rt][1] = 1;
if(op[l] == 2) t[b][rt][0] = a[l], t[b][rt][1] = a[l] ^ 1;
return;
}
int mid = (l + r) >> 1;
build(b, l, mid, ls);
build(b, mid + 1, r, rs);
pushup(b, rt);
}
void update(int b, int pos, int op, int val, int l, int r, int rt)
{
if(l == r)
{
if(op == 0) t[b][rt][0] = 0, t[b][rt][1] = val;
if(op == 1) t[b][rt][0] = val, t[b][rt][1] = 1;
if(op == 2) t[b][rt][0] = val, t[b][rt][1] = val ^ 1;
return;
}
int mid = (l + r) >> 1;
if(pos <= mid) update(b, pos, op, val, l, mid, ls);
else update(b, pos, op, val, mid + 1, r, rs);
pushup(b, rt);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
char s[5];
scanf("%s%d", s, &x[i]);
op[i] = get_op(s[0]);
}
for(int i = 0; i < 10; i++)
{
for(int j = 1; j <= n; j++)
a[j] = (x[j] >> i) & 1;
build(i, 1, n, 1);
}
while(m--)
{
int typ, x;
scanf("%d%d", &typ, &x);
if(typ == 1)
{
int ans = 0;
for(int i = 0; i < 10; i++)
ans += t[i][1][(x >> i) & 1] << i;
printf("%d\n", ans);
}
else
{
char s[5];
int y, opt;
scanf("%s%d", s, &y);
opt = get_op(s[0]);
for(int i = 0; i < 10; i++)
update(i, x, opt, (y >> i) & 1, 1, n, 1);
}
}
return 0;
}