省选测试12
省选测试 12
T1
给定一个长度为\(n\)的数列, 第\(i\)个数字是\(a_i\). 从中任意选一些数字, 那么选出这些数字的分数就是 : 所有满足\(1 \leq L \leq R \leq N\)且区间\([L,R]\)中的数字全部被选中的数对\((L,R)\)的个数 - 被选中的数字之和.
现有\(m\)次询问, 每次询问相互独立, 每次询问给定两个参数\(p,x\), 表示要把\(a_p\)改为\(x\), 问修改之后可以获得的最大分数是多少.
\(n, m \leq 3e5\).
这题调的我真是ex.
斜率优化dp + 分治.
首先最简单的\(dp\)式应该可以想到, 设\(f_i\)为前\(i\)个数的最大得分, 那么转移方程就是 :
\(fab(x) = \frac{x*(x+1)}{2}\), \(sum_i\)代表前\(i\)个数字的和.
有了这个时候我们发现可以拆开优化, 那我们把它拆开看一看 :
如果我们把\((j, f_{j-1} + sum_{j-1} + j^2 - j)\)看成二维平面上的点的话, 那我们就可以用斜率优化了.为了避免实数运算, 我们把所有都乘2. 再变换一下更清晰的式子 :
然后斜率优化就好了.
但这只是没有询问的做法, 有询问怎么办呢?
还是要用斜率优化, 只不过加了个分治.
设\(f_i\)为前\(i\)个数字的最大分数, \(g_i\)为后\(i\)个数字的最大分数, 这两个用上面的方法即可求出.
设\(c_i\)为必须选\(i\)这个数字的最大分数.
那么对于一个询问\(a_p\)修改成\(x\)的答案就是 : \(max(f_{p-1} + g_{p+1}, c_p+a_p-x)\).
那么怎么求出来\(c_i\)呢? 考虑分治, 假如说当前的分治区间是\([l,r]\).
我们设\(R_i\)为从右部选一个位置\(i\), 左部选任意选位置\(j\), 区间\([j,i]\)必须选的最大分数是多少. 这个\(R_i\)将会对区间\([mid+1, i]\)位置的\(c\)造成贡献.同理, 把\(L_i\)设成从左部选一个位置\(i\), 从右部任意选位置\(j\), 区间\([i,j]\)必须选的最大分数是多少.\(L_i\)将会对区间\([i,mid]\)的\(c\)造成贡献.(因为这些\(c\)一定是在\(L_i\)的区间范围内的)
所以现在的问题转换成了求\(R_i, L_i\), 就拿\(R_i\)来举例把, 反正都是一样的.
发现这个式子惊奇的和上面那个长的差不多, 没错, 也可以用斜率优化!
拆开后的式子是这样的 :
然后把\((j, 2f_{j-1}+2sum_{j-1}+j^2 - 3j)\)看成点就好了.
注意这题有个细节, 我调了好久, 就是计算\(f_i, g_i\)的时候, 如果说算出来的是负数的话, 他可以和0区\(max\), 但是计算\(R_i\)的时候就不可以与0区\(max\)了, 因为\(R_i\)有一段区间是必选的.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 3e5 + 5;
const long long inf = 1e18;
int n;
int a[N];
long long f[N], g[N], C[N], R[N], sum[N];
struct point {
long long x, y;
point() {}
point(long long X, long long Y) { x = X; y = Y; }
} sta[N];
point operator - (const point &a, const point &b) { return point(a.x - b.x, a.y - b.y); }
long long Cro(point a, point b) { return a.x * b.y - a.y * b.x; }
double xl(point a, point b) {
return 1.0 * (b.y - a.y) / (b.x - a.x);
}
int top;
void insert(int x, long long y) {
point p = point(x, y);
while(top >= 2 && Cro(sta[top] - sta[top - 1], p - sta[top - 1]) >= 0) top --;
sta[++ top] = p;
}
long long calc(point j, long long i) {
return j.y - i * j.x;
}
long long find(long long k, int f) {
if(!top) return 0;
int l = 2, r = top - 1, mid, ans = 1;
while(l <= r) {
mid = (l + r) >> 1;
double z1 = xl(sta[mid - 1], sta[mid]), z2 = xl(sta[mid], sta[mid + 1]);
if(k <= z1 && k >= z2) { ans = mid; break ; }
else if(k < z1 && k < z2) l = mid + 1;
else r = mid - 1;
}
long long res;
if(f) res = -inf; else res = 0; // *****
return max(max(calc(sta[1], k), calc(sta[top], k)), max(res, calc(sta[ans], k)));
}
void calc(long long *f) {
for(int i = 1;i <= n; i++) sum[i] = sum[i - 1] + a[i]; top = 0;
for(int i = 1;i <= n; i++) {
long long res = find(2ll * i, 0);
f[i] = max(0ll, res - 2ll * sum[i] + 1ll * i * i + i) / 2;
f[i] = max(f[i - 1], f[i]);
insert(i, 2ll * f[i] + 2ll * sum[i] + 1ll * i * i - i);
}
}
void solve(int l, int r) {
if(l == r) { C[l] = max(C[l], 1ll - a[l]); return ; }
int mid = (l + r) >> 1;
top = 0;
for(int i = l;i <= mid; i++) insert(i, 2ll * f[i - 1] + 2ll * sum[i - 1] + 1ll * i * i - 3ll * i);
for(int i = mid + 1;i <= r; i++) R[i] = (find(2ll * i, 1) - 2ll * sum[i] + 2ll * g[i + 1] + 1ll * i * i + 3ll * i + 2) / 2;
long long Max = -inf;
for(int i = r;i > mid; i--) {
Max = max(Max, R[i]);
C[i] = max(C[i], Max);
}
top = 0;
for(int i = mid + 1;i <= r; i++) insert(i, 2ll * g[i + 1] - 2ll * sum[i] + 1ll * i * i + 3ll * i);
for(int i = l;i <= mid; i++) R[i] = (find(2ll * i, 1) + 2ll * sum[i - 1] + 2ll * f[i - 1] + 1ll * i * i - 3ll * i + 2) / 2;
Max = -inf;
for(int i = l;i <= mid; i++) {
Max = max(Max, R[i]);
C[i] = max(C[i], Max);
}
solve(l, mid); solve(mid + 1, r);
}
void Work() {
calc(f); reverse(a + 1, a + n + 1);
calc(g); reverse(a + 1, a + n + 1); reverse(g + 1, g + n + 1);
// for(int i = 1;i <= n; i++) cout << f[i] << " "; cout << "\n";
// for(int i = 1;i <= n; i++) cout << g[i] << " "; cout << "\n";
for(int i = 1;i <= n; i++) sum[i] = sum[i - 1] + a[i];
for(int i = 1;i <= n; i++) C[i] = -inf;
solve(1, n);
// for(int i = 1;i <= n; i++) cout << C[i] << " "; cout << "\n";
int m = read();
for(int i = 1, p, x;i <= m; i++) {
p = read(); x = read();
printf("%lld\n", max(f[p - 1] + g[p + 1], C[p] + a[p] - x));
}
}
int main() {
freopen("score.in","r",stdin); freopen("score.out","w",stdout);
n = read();
for(int i = 1;i <= n; i++) a[i] = read();
Work();
fclose(stdin); fclose(stdout);
return 0;
}
T2
有\(n\)个人, 每个人有\(a_i\)个物品. 每个人刚开始拿的都是同一种物品.
有\(m\)次操作, 给定两个参数\(x,y\), 表示\(x,y\)这两个人可以拿自己的任意一种物品和对方交换, 当然也可以不交换. 问第一个人在\(m\)次操作结束后手上最多有多少不同的物品.
\(n,m \leq 3000\).
最大流.
首先我们可以先转化一下问题 :
我们要求第一个人最多的种数而不是个数, 所以我们不妨设第一个人每种物品只拿了一个, 这样显然不会使答案变得更劣.
那么我们就可以认为, 每个人手上只有1个物品, 但是手里最多可以拿\(a_i\)个物品.
我们考虑这么建图, 从原点向每个人连一条流量为1的边, 然后把每个人分裂成\(m\)个点, 第\(m\)次操作就往\(x,y\)中间连一条双向边, 流量都是1, 第\(i\)个人分裂出的第\(j\)个点要向第\(j+1\)个点连一条流量为\(a_i\)的边, 最后第1个人的第\(m\)个分裂点向汇点连一条流量为\(a_1\)的边, 然后跑最大流就可以了.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6, inf = 1e9, M = 1e7;
int n, m, s, t, tot, cnt;
int a[N], d[N], las[N], cur[N], head[N];
struct edge { int c, to, nxt; } e[M];
void Clear() {
cnt = 1; tot = 0;
memset(head, 0, sizeof(head));
memset(las, 0, sizeof(las));
}
void add(int x, int y, int z) {
// cout << x << " " << y << " " << z << "++++\n";
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].c = z;
e[++ cnt].nxt = head[y]; head[y] = cnt; e[cnt].to = x; e[cnt].c = 0;
}
void Init() {
n = read(); m = read();
for(int i = 1;i <= n; i++) a[i] = read();
for(int i = 1, x, y;i <= m; i++) {
x = read(); y = read();
int t1 = ++ tot, t2 = ++ tot;
add(t1, t2, 1); add(t2, t1, 1);
if(las[x]) add(las[x], t1, a[x]); else add(las[x], t1, 1);
if(las[y]) add(las[y], t2, a[y]); else add(las[y], t2, 1);
las[x] = t1; las[y] = t2;
}
t = ++ tot; add(las[1], t, a[1]);
}
int bfs() {
for(int i = s;i <= t; i++) d[i] = inf, cur[i] = head[i];
queue <int> q; q.push(s); d[s] = 0;
while(q.size()) {
int x = q.front(); q.pop();
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
// cout << x << " " << y << "!!!\n";
if(e[i].c && d[y] == inf) {
d[y] = d[x] + 1; q.push(y);
if(y == t) return 1;
}
}
}
return 0;
}
int dfs(int x, int want) {
if(x == t || !want) return want;
int f, get = 0;
for(int i = cur[x]; i && want; i = e[i].nxt) {
int y = e[i].to;
if(!e[i].c || d[y] != d[x] + 1) continue ;
f = dfs(y, min(want, e[i].c));
if(!f) d[y] = -1;
else want -= f, get += f, e[i].c -= f, e[i ^ 1].c += f;
cur[x] = i;
}
return get;
}
void Work() {
int res = 0;
while(bfs()) { res += dfs(s, inf); }
printf("%d\n", res);
}
int main() {
freopen("collection.in","r",stdin); freopen("collection.out","w",stdout);
for(int T = read(); T ; T --) {
Clear(); Init(); Work();
}
fclose(stdin); fclose(stdout);
return 0;
}
/*
2
3 3
3 2 1
2 3
1 2
1 2
3 3
3 1 2
2 3
1 2
1 2
*/
T3
咕咕咕咕咕咕咕........