26杭电暑期第九场 贪心|bitset|分层图DP|线性基|笛卡尔树|KMP|随机哈希|博弈|随机游走|二位数点|分类讨论
1001
贪心
n个商品,如果一个商品价格超过当前钱的一半,则不会买。问至少准备多少钱,才能买下所有商品,并且商品购买顺序不确定。
显然最贵的商品,最可能超过剩余钱的一半,导致买不了。并且如果所有商品价格一样,最后一个买的商品,前面已经花的钱最多,剩余钱最少,最可能超过钱的一半,导致买不了。综合一下,越贵,越靠后的越可能买不了。
最严格的条件是:最贵的东西最后一个买,如果这个情况钱都够,前面的也都够。要满足这个条件,至少准备∑ai+max(ai)\sum a_i+\max(a_i)∑ai+max(ai)实际上就够了。
void solve() {
int n;
cin >> n;
int sum = 0, mx = 0;
rep(i, 1, n) {
int x;
cin >> x;
sum += x;
mx = max(mx, x);
}
cout << sum + mx << '\n';
}
1003
bitset 分层图 最短路 DP
一个路径的邻域定义为这个路径所有点,加上路径所有点的邻居。问从s到t的最短路中,邻域最小多小,达到这个最小值的路径有多少个。
首先只用考虑在最短路上的边,这很好做。分别从s,t出发跑最短路,枚举所有边,如果一个边(u,v,w)(u,v,w)(u,v,w)满足ds(u)+dt(v)+w=最短路d_s(u)+d_t(v)+w=最短路ds(u)+dt(v)+w=最短路,则这条边在最短路上。
所有在最短路上的点,根据到s的距离,实际上构成了一个分层图,第i层只和i-1,i+1之间存在边。并且i层的一个点,只会和i-2,i-1,i+1,i+2这几层的邻域有交点,可以这样考虑:如果和i-3层的邻域有交点,那么i-3层经过这个交点中转,可以到第i层,那么i-3层和i层之间应该只隔了一层!,但实际隔了两层,矛盾。
于是可以定义dp(a,b)dp(a,b)dp(a,b)表示在这个分层图上,向下移动形成一条路径,最后两个点是a,b,的最小邻域大小。为了维护方案数,还可以再维护另一个cnt数组cnt(a,b)cnt(a,b)cnt(a,b)表示分层图上一条向下的路径,最后两个点是a,b,邻域大小最小的方案数。
转移时移动到下一层的一个点c,c在i层的话,a,b分别在i-2,i-1层,那么c对邻域大小的贡献,应该是c的邻域,删掉路径上在其他点的邻域后的大小。根据前面的分析,和c的邻域有交集的,只有前两层的a,b两点,于是c对邻域大小的贡献就是∣Cc∖(Ca∪Cb)∣\left|C_c\setminus(C_a\cup C_b)\right|∣Cc∖(Ca∪Cb)∣,CxC_xCx是x的邻域集合。
维护最小值的同时,维护最小值方案数是简单的这里不细说了。这个转移难点在于求∣Cc∖(Ca∪Cb)∣\left|C_c\setminus(C_a\cup C_b)\right|∣Cc∖(Ca∪Cb)∣这个集合的大小。朴素做法是枚举a,b,c的邻居,更新set,这个dp状态数O(n2)O(n^2)O(n2),每个状态可能的转移有O(n)O(n)O(n)个(因为b的邻居c可能有O(n)O(n)O(n)个),整体复杂度已经O(n3)O(n^3)O(n3)了,转移必须很快,复杂度应该小于O(n)O(n)O(n)。
注意这里我们只关系几个集合取并,做差后的大小,那么可以用一个bitset表示一个点的邻域集合,这样可以支持集合取并,做差,查询集合大小,并且复杂度只有O(n/w)O(n/w)O(n/w)。注意开始需要设置每个点的邻域集合,要把点本身和它的邻居放进去,这里的邻居不是分层图上的,是原始图上所有邻居。
整体复杂度O(n4/w)O(n^4/w)O(n4/w)
void solve() {
int n, m, s, t;
cin >> n >> m >> s >> t;
vvi g(n + 1);
rep(i, 1, m) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vi ds(n + 1, inf);
queue<int>q;
q.push(s);
ds[s] = 0;
while (q.size()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (ds[u] + 1 < ds[v]) {
ds[v] = ds[u] + 1;
q.push(v);
}
}
}
vi dt(n + 1, inf);
q = queue<int>();
q.push(t);
dt[t] = 0;
while (q.size()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (dt[u] + 1 < dt[v]) {
dt[v] = dt[u] + 1;
q.push(v);
}
}
}
vvi e(n + 1, vi(n + 1));
vi vis(n + 1);
rep(i, 1, n) {
for (int j : g[i]) {
if (ds[i] + dt[j] + 1 == ds[t]) {
e[i][j] = e[j][i] = 1;
vis[i] = vis[j] = 1;
}
}
}
int mx = ds[t];
vvi layer(mx + 1);
vector<bitset<505>>st(n + 1);
rep(i, 1, n) {
if (vis[i]) {
layer[ds[i]].push_back(i);
for (int j : g[i]) {
st[i][j] = 1;
}
st[i][i] = 1;
}
}
vvi dp(n + 1, vi(n + 1, inf));
vvi cnt(n + 1, vi(n + 1));
for (int a : layer[0]) {
for (int b : layer[1]) {
if (!e[a][b]) continue;
int val = (st[a] | st[b]).count();
dp[a][b] = val;
cnt[a][b] = 1;
}
}
rep(i, 2, mx) {
for (int a : layer[i - 2]) {
for (int b : layer[i - 1]) {
if (!e[a][b]) continue;
for (int c : layer[i]) {
if (!e[b][c]) continue;
int add = (st[c] & (~(st[a] | st[b]))).count();
if (dp[a][b] + add < dp[b][c]) {
dp[b][c] = dp[a][b] + add;
cnt[b][c] = cnt[a][b];
} else if (dp[b][c] == dp[a][b] + add) {
cnt[b][c] += cnt[a][b];
cnt[b][c] %= M2;
}
}
}
}
}
int mn = inf, ans = 0;
for (int a : layer[mx - 1]) {
for (int b : layer[mx]) {
if (!e[a][b]) continue;
if (dp[a][b] < mn) {
mn = dp[a][b];
ans = cnt[a][b];
} else if (dp[a][b] == mn) {
ans += cnt[a][b];
ans %= M2;
}
// cout << a << ' ' << b << ' ' << ans << ' ' << mn << '\n';
}
}
cout << mn << ' ' << ans << '\n';
}
1004
线性基
一个数组a,一个和a长度相同的01序列b,初始全0,每次操作可以在b上选一个长度k的子区间取反。每次询问给一个x,问任意次操作后,(⊕i=1naibi)⊕x(\oplus_{i=1}^n a_ib_i)\oplus x(⊕i=1naibi)⊕x的最大值
注意到⊕i=1naibi\oplus_{i=1}^n a_ib_i⊕i=1naibi这部分,在bi=0/1b_i=0/1bi=0/1可以任取时,类似于一个线性基可表示的数字集合,查询它异或上x的最大值,就是个线性基最大值查询。
关键是维护这个线性基,每次操作可以选一个长度k的子区间,全部取反,那么实际上这个线性基内的元素就是⊕i=1kai,⊕i=2k+1ai...⊕i=n−k+1nai\oplus_{i=1}^k a_i,\oplus_{i=2}^{k+1} a_i...\oplus_{i=n-k+1}^n a_i⊕i=1kai,⊕i=2k+1ai...⊕i=n−k+1nai这些元素。
把这些元素插入线性基后,正常查询即可。插入这些元素可以使用异或前缀和或者滑窗。
class LinearBasis {
private:
static const int MN = 63;
long long a[MN + 1], tmp[MN + 1];
int sz;
bool flag;
public:
LinearBasis() : sz(0), flag(false) {
memset(a, 0, sizeof(a));
memset(tmp, 0, sizeof(tmp));
}
bool insert(long long x) {
for (int i = MN; i >= 0; i--) {
if (x & (1LL << i)) {
if (!a[i]) {
a[i] = x;
sz++;
return 1;
}
x ^= a[i];
}
}
flag = true;
return 0;
}
bool check(long long x) {
for (int i = MN; i >= 0; i--) {
if (x & (1LL << i)) {
if (!a[i]) return false;
x ^= a[i];
}
}
return true;
}
long long queryMax(long long res = 0) {
for (int i = MN; i >= 0; i--) {
res = max(res, res ^ a[i]);
}
return res;
}
} lb;
void solve() {
int n, len, q;
cin >> n >> len >> q;
vi sum(n + 1);
rep(i, 1, n) {
int x;
cin >> x;
sum[i] = sum[i - 1] ^ x;
}
LinearBasis lb;
rep(i, len, n) {
lb.insert(sum[i]^sum[i - len]);
}
rep(i, 1, q) {
int x;
cin >> x;
cout << lb.queryMax(x) << '\n';
}
}
1005
笛卡尔树 KMP 单调栈
问对于每个前缀[1,i][1,i][1,i],满足[1,j][i−j+1,i][1,j][i-j+1,i][1,j][i−j+1,i]这两个区间构建的小根笛卡尔树形态相同的最大jjj是多少。注意是形态相同,就是树的拓扑相同,不是节点权值相同。
注意到,这个问题类似于求每个前缀的最长公共前后缀,这可以KMP。但是KMP是字符串匹配,这里的匹配条件是什么?
回顾笛卡尔树的构建过程,
// stk 维护笛卡尔树中节点对应到序列中的下标
for (int i = 1; i <= n; i++) {
int k = top; // top 表示操作前的栈顶,k 表示当前栈顶
while (k > 0 && w[stk[k]] > w[i]) k--; // 维护右链上的节点
if (k) rs[stk[k]] = i; // 栈顶元素.右儿子 := 当前元素
if (k < top) ls[i] = stk[k + 1]; // 当前元素.左儿子 := 上一个被弹出的元素
stk[++k] = i; // 当前元素入栈
top = k;
}
实际上决定笛卡尔树形态的,只有每个点插入时的这个while退栈,会执行多少次,这构成一个数组preprepre,preipre_iprei表示aia_iai会退栈多少次,或者会退栈到序列中第几个元素。如果两个序列的preprepre数组完全相同,即使值不同,构建的笛卡尔树也是相同的。
这里preipre_iprei显然就是aia_iai往左走,第一个不大于aia_iai的元素位置。这可以单调栈O(n)O(n)O(n)计算。
然后把kmpkmpkmp的匹配条件,从s[j+1]=s[i]s[j+1]=s[i]s[j+1]=s[i],改成i−prei=j−preji-pre_i=j-pre_ji−prei=j−prej即可,这表示的就是i,ji,ji,j两个位置,在建笛卡尔树时需要退栈的次数相等。
注意边界情况,i,j如果左侧都没有更小元素了,单调栈跑出来的prei,jprejpre_i,jpre_jprei,jprej应该都是0,也是可以的。此外可能[1,j][1,j][1,j]内的axa_xax左侧没有更小的,但是[i−j+1,j][i-j+1,j][i−j+1,j]内的aya_yay,左侧有更小的,但不在[i−j+1,i][i-j+1,i][i−j+1,i]的范围内,这同样视为左侧没有更小的。具体看check函数内的判断
注意这里套的是1base的kmp模板,已匹配前缀长度为p,那么接下来应该检查(p+1,i)(p+1,i)(p+1,i)是否匹配。
void solve() {
int n;
cin >> n;
vi a(n + 1);
rep(i, 1, n) {
cin >> a[i];
}
vi pre(n + 1);
stack<int>s;
rep(i, 1, n) {
while (s.size() && a[s.top()] > a[i]) {
s.pop();
}
if (s.size()) {
pre[i] = s.top();
}
s.push(i);
}
vi f(n + 1);
f[1] = 0;
auto check = [&](int i, int j)->bool{
if (pre[j] == 0 && (pre[i] == 0 || pre[i] < i - j)) {
return 1;
} else {
return i - pre[i] == j - pre[j];
}
};
int p = 0;
rep(i, 2, n) {
while (p && !check(i, p + 1)) {
p = f[p];
}
if (check(i, p + 1)) {
p++;
}
f[i] = p;
}
rep(i, 1, n) {
cout << f[i] << ' ';
}
cout << '\n';
}
1007
随机哈希 树状数组
每个物品有两种属性x,c。两类操作:
- 单点修改
- 询问区间内,是否每个x的所有k种c出现次数相等
如果n,q=2e5n,q=2e5n,q=2e5,这题可以带修莫队,复杂度O(n53)O(n^\frac53)O(n35)。对于每种x,维护一个cnt数组记录每种c的出现次数,以及一个变量维护c的值有几种,只有一种时这个x合法。对于每个x都要满足,那么再维护一个变量,记录满足条件的x的个数。
但这题数据量n,q=2e6n,q=2e6n,q=2e6,莫队直接废了。而莫队其实已经是最快的确定性算法了。想要更快,就得用随机化乱搞了。
其实这个问题是经典随机化问题,问一个区间内k种元素出现次数是否相等,考虑随机哈希,把每种元素映射到一个随机值,并且第k种元素的哈希值,是前k-1种元素哈希值之和的相反数。这样如果所有k种元素都出现一次,他们的哈希值之和就是0,如果所有k种元素出现次数都相等,哈希值之和仍然是0。对于每一种x的k种值,都分别这样哈希,树状数组维护元素哈希值,查询区间哈希值之和是否为0,即可判断区间内是否对每个x,k种元素出现次数都相等。
注意x种类是O(n)O(n)O(n)的,如果开始对于n*k种元素全部给一个哈希值,复杂度是O(nk)O(nk)O(nk),n,k同阶,那么是O(n2)O(n^2)O(n2)的,太慢了。考虑动态哈希,也就是查询到一个元素时,如果这个元素哈希值被缓存了则直接返回,如果没有哈希值,再现场哈希并缓存。这样总的哈希次数是O(n+q)O(n+q)O(n+q)的。
哈希时,为了保证每个x的k种元素,哈希值之和为0。维护一下当前x已经出现的不同c种类数,以及这些k的哈希值之和,前k-1种c的哈希值,都使用随机数。当出现第k种c时,哈希值不使用随机数,而是前k-1种哈希值之和的相反数。
为了保证足够的随机性,使用mt19937随机数生成器,并使用当前时间作为种子。
mt19937_64 rnd(
chrono::steady_clock::now().time_since_epoch().count()
);
void solve() {
int n, m, k;
cin >> n >> m >> k;
vi a(n + 1), b(n + 1);
rep(i, 1, n) {
cin >> a[i];
}
rep(i, 1, n) {
cin >> b[i];
}
map<pii, int>mp;
unordered_map<int, unordered_set<int>>st;
unordered_map<int, int>sum;
auto get = [&](int x, int c)->int{
if (mp.count({x, c})) {
return mp[ {x, c}];
}
if (st[x].size() == k - 1 && !st[x].count(c)) {
st[x].insert(c);
return mp[ {x, c}] = -sum[x];
}
int v = rnd();
sum[x] += v;
st[x].insert(c);
return mp[{x, c}] = v;
};
FenwickTree tr(n + 10);
rep(i, 1, n) {
tr.update(i, get(a[i], b[i]));
}
rep(i, 1, m) {
int op;
cin >> op;
if (op == 1) {
int p, x, c;
cin >> p >> x >> c;
int v = get(a[p], b[p]);
int nv = get(x, c);
tr.update(p, nv - v);
a[p] = x;
b[p] = c;
} else {
int l, r;
cin >> l >> r;
int sum = tr.rangeQuery(l, r);
if (sum == 0) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
}
}
1011
博弈 随机游走 概率
先手在树上选一个叶子,后手看到先手操作后,也选一个叶子。一个人从根也就是1号点出发随机游走,走到先手选的叶子就是先手赢,走到后手的叶子则是后手赢,问双方都最优操作下,先手赢的概率?
假设先手后手分别选了叶子u,v。那么实际上,由于随机游走是无后效性的,我们只用考虑路径(u,v)(u,v)(u,v)这条链上的点,从链上点随机游走到树上其他点,再回到链上出发点,不管中间怎么走的,对结果概率仍然无影响。甚至在链上怎么走的也没有影响,唯一有影响的是当前位于链上哪个点。那么考虑第一次进入这个链时位于哪个点,根据这个点在链上的位置就能确定答案。
第一次进入路径(u,v)(u,v)(u,v)处于的点显然是lca(u,v)lca(u,v)lca(u,v),那么问题转化为,一个链,当前起点距离左端点x,距离右端点y,问随机游走,第一个到达的端点是左端点的概率?
这可以手玩,找规律。由于这是个马尔可夫过程,概率肯定收敛,也可以模拟一定轮数,计算概率,观察收敛值大概多少。这也是一维随机游走的经典结论,可以记住。总之答案就是yx+y\frac{y}{x+y}x+yy,这样比较符合常理,如果x小于y,那么游走到左端点的概率应该更大,更具体来说,游走到一个点的概率,和起点到这个点的距离成反比。
有了这个结论,如果两人选择的叶子是确定的,答案也就确定了。枚举全部O(n2)O(n^2)O(n2)个叶子,复杂度也可以接受,可以在O(logn)O(\log n)O(logn)甚至O(1)O(1)O(1)内计算出lcalcalca,然后就可以O(1)O(1)O(1)计算答案了。O(1)O(1)O(1)确定lcalcalca是因为这题允许O(n2)O(n^2)O(n2),可以先类似启发式合并,在dfs过程中以O(nlogn)O(n\log n)O(nlogn)或O(n2)O(n^2)O(n2)的复杂度计预处理出任意两点的lcalcalca
考虑这还是个博弈,因此不能随便枚举O(n2)O(n^2)O(n2)个叶子对,后手会根据显瘦的选择,选择对自己最优的叶子,这实际上是个minmax博弈。那么实际应该外层枚举先手的选择,内层枚举后手的选择,我们维护的答案是先手胜率,那么先手想让他尽量大,后手想让他尽量小,内层循环应该维护最小胜率,外层循环应该枚举最大胜率。
并且这题要输出模意义下分数,不能用浮点,但又要比较大小,取模分数无法比较大小。考虑使用一个分数类,保存整型的分子分母,并且定义一个比较大小的函数。
实际上这题的数据有点炸骗。手玩或者分析可以发现,先手最优的选择一定是深度最小的叶子,只需要一层循环,枚举后手选哪个叶子即可,枚举量可以降低到O(n)O(n)O(n),考虑lcalcalca的计算,整体复杂度可以降低到O(nlogn)O(n\log n)O(nlogn)
以下代码是一个O(n2)O(n^2)O(n2)枚举的实现
struct frac {
int p, q;
};
void solve() {
int n;
cin >> n;
vi INV(2 * n + 10);
rep1(i, 2 * n, 1) {
INV[i] = inv(i, M2);
}
vvi g(n + 1);
rep(i, 1, n - 1) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vvi s(n + 1);
vvi lca(n + 1, vi(n + 1));
vi d(n + 1);
auto &&dfs = [&](auto &&dfs, int u, int f)->void{
for (int v : g[u]) {
if (v == f)continue;
d[v] = d[u] + 1;
dfs(dfs, v, u);
for (int x : s[v]) {
for (int y : s[u]) {
lca[x][y] = lca[y][x] = u;
}
}
for (int x : s[v]) {
s[u].push_back(x);
}
}
for (int x : s[u]) {
lca[x][u] = lca[u][x] = u;
}
s[u].push_back(u);
};
dfs(dfs, 1, 0);
auto les = [](frac a, frac b)->bool{
return a.p * b.q < b.p * a.q;
};
frac mx;
mx.p = 0;
mx.q = 1;
rep(i, 2, n) {
if (g[i].size() != 1)continue;
frac mn;
mn.p = mn.q = 2;
rep(j, 2, n) {
if (i == j)continue;
if (g[j].size() != 1)continue;
int l = lca[i][j];
int d1 = d[i] - d[l];
int d2 = d[j] - d[l];
frac t;
t.p = d2;
t.q = d1 + d2;
if (les(t, mn)) {
mn = t;
}
}
if (mn.p == 2 && mn.q == 2) {
continue;
}
if (les(mx, mn)) {
mx = mn;
}
// cout << i << ' ' << mx.p << ' ' << mx.q << '\n';
}
// cout << mx.p << ' ' << mx.q << ' ';
cout << mx.p*INV[mx.q] % M2 << '\n';
// cout << mx.p*inv(mx.q, M2) % M2 << '\n';
}
1012
树状数组 二位数点 分类讨论 推式子
数组权值定义为f(a)=∑i=1n−1(ai−ai+1)2f(a)=\sum_{i=1}^{n-1}(a_i-a_{i+1})^2f(a)=∑i=1n−1(ai−ai+1)2。可以交换一组元素(ai,aj)(a_i,a_j)(ai,aj)。问有多少种交换方法,交换后权值变大。
显然一个aia_iai只能和相邻的两个元素ai−1,ai+1a_{i-1},a_{i+1}ai−1,ai+1产生贡献,交换ai,aja_i,a_jai,aj,可以O(1)O(1)O(1)计算出权值改变量
接下来需要分类讨论,因为ai,aja_i,a_jai,aj的相邻元素,可能有重合,以及可能位于边界根本没有相邻元素
- 最一般的情况,不位于边界,ai−1,ai+1,aj−1,aj+1a_{i-1},a_{i+1},a_{j-1},a_{j+1}ai−1,ai+1,aj−1,aj+1都存在,且没有重合。把f(A′)>f(A)f(A')>f(A)f(A′)>f(A)完全展开,可以得到i,ji,ji,j交换后变大,等价于ai<aja_i\lt a_jai<aj且ai−1+ai+1<aj−1+aj+1a_{i-1}+a_{i+1}\lt a_{j-1}+a_{j+1}ai−1+ai+1<aj−1+aj+1,可以转化为二位数点,这是简单的,扫描线+树状数组即可,这里的i,ji,ji,j都属于[2,n−1][2,n-1][2,n−1]
- 一种边界是i=1,i=ni=1,i=ni=1,i=n,只有一侧有相邻元素,但这样的点对只有O(n)O(n)O(n)个,可以全部暴力,两次循环,第一次枚举i=1,j=[2,n−1]i=1,j=[2,n-1]i=1,j=[2,n−1],第二次枚举i=n,j=[2,n−1]i=n,j=[2,n-1]i=n,j=[2,n−1],最后还有一个i=1,j=ni=1,j=ni=1,j=n
- 另一种边界是j=i+1j=i+1j=i+1,原来的式子不成立了,但仍然只有O(n)O(n)O(n)对,考虑直接暴力。问题是第一种情况的二位数点,也会错误地计算这种情况,因此既然我们把这种情况独立出来算了,为了不重不漏,应该在第一种二位数点里排除这种情况。具体来说,二维数点查询点iii的答案时,临时把i−1,i+1i-1,i+1i−1,i+1的点从树状数组中删掉,查询完了再加回来。
void solve() {
int n;
cin >> n;
vi a(n + 10);
int mx = 0;
rep(i, 1, n) {
cin >> a[i];
mx = max(mx, a[i]);
}
if (n == 1) {
cout << 0 << '\n';
return;
}
auto f = [&](int x, int y, int z)->int{
return (a[y] - a[x]) * (a[y] - a[x]) + (a[y] - a[z]) * (a[y] - a[z]);
};
int ans = 0;
//abc def
vvi bin(mx + 10);
vi b(n + 1);
rep(i, 2, n - 1) {
b[i] = a[i - 1] + a[i + 1];
bin[a[i]].push_back(i);
}
FenwickTree tr(2 * mx + 10);
rep(i, 1, mx) {
if (!bin[i].size()) {
continue;
}
for (int id : bin[i]) {
int v = b[id];
if (v - 1 >= 1) {
if (id - 1 >= 2) {
if (a[id - 1] < i) {
tr.update(b[id - 1], -1);
}
}
if (id + 1 <= n - 1) {
if (a[id + 1] < i) {
// cout << "del:" << i << ' ' << id + 1 << ' ' << b[id + 1] << '\n';
tr.update(b[id + 1], -1);
}
}
// cout << "ans:" << i << ' ' << tr.query(v - 1) << '\n';
ans += tr.query(v - 1);
if (id - 1 >= 2) {
if (a[id - 1] < i) {
tr.update(b[id - 1], 1);
}
}
if (id + 1 <= n - 1) {
if (a[id + 1] < i) {
tr.update(b[id + 1], 1);
}
}
}
}
for (int id : bin[i]) {
// cout << "add:" << i << ' ' << id << ' ' << b[id] << '\n';
tr.update(b[id], 1);
}
}
// cout << ans << ' ';
rep(i, 2, n - 1) {
//f(a)>=f(a')
int v1 = (a[1] - a[2]) * (a[1] - a[2]) + f(i - 1, i, i + 1);
swap(a[1], a[i]);
int v2 = (a[1] - a[2]) * (a[1] - a[2]) + f(i - 1, i, i + 1);;
swap(a[1], a[i]);
// cout<<i<<' '<<v1<<' '<<v2<<'\n';
if (v2 > v1) {
ans++;
}
}
// cout << ans << ' ';
rep(i, 2, n - 1) {
//f(a)>=f(a')
int v1 = (a[n] - a[n - 1]) * (a[n] - a[n - 1]) + f(i - 1, i, i + 1);
swap(a[n], a[i]);
int v2 = (a[n] - a[n - 1]) * (a[n] - a[n - 1]) + f(i - 1, i, i + 1);
swap(a[n], a[i]);
// cout<<i<<' '<<v1<<' '<<v2<<'\n';
if (v2 > v1) {
ans++;
}
}
// cout << ans << ' ';
int v1 = (a[1] - a[2]) * (a[1] - a[2]) + (a[n] - a[n - 1]) * (a[n] - a[n - 1]);
swap(a[1], a[n]);
int v2 = (a[1] - a[2]) * (a[1] - a[2]) + (a[n] - a[n - 1]) * (a[n] - a[n - 1]);
if (v2 > v1) {
ans++;
}
swap(a[1], a[n]);
// cout << ans << '\n';
rep(i, 2, n - 2) {
int v1 = f(i - 1, i, i + 1) + f(i, i + 1, i + 2);
swap(a[i], a[i + 1]);
int v2 = f(i - 1, i, i + 1) + f(i, i + 1, i + 2);
swap(a[i], a[i + 1]);
if (v2 > v1) {
ans++;
}
}
cout << ans << '\n';
}
更多推荐


所有评论(0)