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\),对于每个 \(a_i\),不断除以 \(mindiv\),就可以得到每个质因子了,也就求出了 \(cnt\) 数组。
也可以不处理 \(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\),转移时,枚举 \(j\),若 \(|h_i-h_j| \le x\times (i-j)\),就说明可以通过修改 \((i,j)\) 中间的 \(i-j-1\) 楼使得前 \(i\) 栋楼合法
\(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;
}