ABC396 拆位+二分图+构造 差分/变化量 维护逆序对
构造 二分图 拆位给一个序列A,给一堆约束xyz,要求Ax⊕Ayz需要构造一个A序列,使得∑Ai最小,或报告无解。位运算,尤其是异或,他是不进位的,不同位之间完全没有影响,考虑拆位。对于每一位就是一个只有01的情况。这样约束只有两类,z0/1,也就是AxAy在这一位相同0,或不同1。利用这个关系建图,会发现我们实际上就得到了一个二分图。于是问题转化成给一堆关系,每个关系是xy是同一类,或
E
构造 二分图 拆位
给一个序列AAA,给一堆约束(x,y,z)(x,y,z)(x,y,z),要求Ax⊕Ay=zA_x \oplus A_y =zAx⊕Ay=z
需要构造一个AAA序列,使得∑Ai\sum A_i∑Ai最小,或报告无解。
位运算,尤其是异或,他是不进位的,不同位之间完全没有影响,考虑拆位。对于每一位就是一个只有0,10,10,1的情况。这样约束只有两类,z=0/1z=0/1z=0/1,也就是Ax,AyA_x,A_yAx,Ay在这一位相同(0)(0)(0),或不同(1)(1)(1)。利用这个关系建图,会发现我们实际上就得到了一个二分图。
于是问题转化成给一堆关系,每个关系是x,yx,yx,y是同一类,或不是同一类。现在问这些约束能否都成立,互不冲突。在此基础上还要使得这一位是000对应的那一个集合,尽可能小。
这显然可以染色法/拓展域并查集判断二分图。如果是二分图就能能满足所有约束。满足约束后,还要最小化000集合的大小,可以比较0,10,10,1集合的大小,如果000集合较小,就交换0,10,10,1集合,这是可以交换的,因为约束只规定了,两个点的集合是否相同,没有规定每个点到底是000还是111。
这样计算出每一位的结果后,拼起来就知道所有AiA_iAi的值了
void solve()
{
int n, m;
cin >> n >> m;
vvi e;
rep(i, 1, m)
{
int x, y, z;
cin >> x >> y >> z;
e.push_back({x, y, z});
}
vi ans(n + 1);
rep(i, 0, 30)
{
vector<vector<pii>> g(n + 1);
for (auto &t : e)
{
g[t[0]].push_back({t[1], t[2] >> i & 1});
g[t[1]].push_back({t[0], t[2] >> i & 1});
}
vi co(n + 1, -1);
rep(j, 1, n)
{
if (co[j] == -1)
{
int c0 = 0, c1 = 0;
vi cur;
auto &&dfs = [&](auto &&dfs, int u, int c) -> bool
{
co[u] = c;
cur.push_back(u);
if (c == 0)
c0++;
else
c1++;
for (auto [v, w] : g[u])
{
if (co[v] == -1 && !dfs(dfs, v, c ^ w))
return false;
if (w == 1 && co[v] == c || w == 0 && co[v] != c)
return false;
}
return true;
};
if (!dfs(dfs, j, 0))
{
cout << -1;
return;
}
if (c1 > c0)
{
for (int x : cur)
{
co[x] = -1;
}
dfs(dfs, j, 1);
}
}
}
rep(j, 1, n)
{
if (co[j] == 1)
{
ans[j] += (1ll << i);
}
}
}
rep(i, 1, n)
{
cout << ans[i] << ' ';
}
}
F
逆序对 线段树
初始给一个序列,接下来mmm轮,每轮给所有元素加111,模mmm。问每一轮操作完序列的逆序对个数。或者说第kkk轮是ai+ka_i+kai+k,问k=0,...,m−1k=0,...,m-1k=0,...,m−1每一轮的逆序对个数。
一个静态序列的逆序对是好求的,但是这相当于给我们nnn个序列,肯定不能每个都O(nlogn)O(n\log n)O(nlogn)求
这种问题一般就两个思路,要么是每一次操作后,新的结果只是在原本结果的基础上,有一个变化量,我们初始维护一下第一轮的结果,后面每一轮只用维护变化量即可
另一种思路是考虑贡献法,枚举每个元素,计算每个元素,对一系列情况的答案的贡献。
我开始想的是贡献法。考虑每一对元素(ai,aj),i<j(a_i,a_j),i<j(ai,aj),i<j,如果ai>aja_i>a_jai>aj,那么这对元素,有贡献的kkk实际上是两个区间[0,m−(ai−1)],[m−aj,m−1][0,m-(a_i-1)],[m-a_j,m-1][0,m−(ai−1)],[m−aj,m−1]。也就是只有aia_iai已经被取模了,但aja_jaj还没有被取模这段时间,是没贡献的,其他都有贡献。
如果ai<aja_i<a_jai<aj,那么在aja_jaj已经被取模,aia_iai还没被取模的这段时间,会形成逆序对,也就是[m−aj,m−ai−1][m-a_j,m-a_i-1][m−aj,m−ai−1]
每一个的贡献了都是一个差分,但似乎还是不能做,因为这样的元素对有n2n^2n2对。不能每一个都去差分。考虑类扫描线的思想,枚举aja_jaj,一次处理aja_jaj和所有aia_iai的贡献。注意到对于一个aja_jaj,贡献里和jjj相关的都是可以立刻确定的,但是iii可能有多个,不能确定。那么实际上我们这个时候就是只能确定aja_jaj相关的,不用去处理aia_iai的。以[m−aj,m−ai−1][m-a_j,m-a_i-1][m−aj,m−ai−1]为例,枚举到aja_jaj的时候,只用计算[1,j][1,j][1,j]里比aja_jaj小的元素个数kkk,那么就有kkk次[m−aj,m][m-a_j,m][m−aj,m]区间加的贡献。对于m−ai−1m-a_i-1m−ai−1这一端的,则是我们在枚举aia_iai时,计算[i,n][i,n][i,n]里有多少个比aia_iai大的元素,对[m−ai,m][m-a_i,m][m−ai,m]进行kkk此区间减
struct Tree
{
#define ls u << 1
#define rs u << 1 | 1
struct Node
{
int l, r;
ll mx, add;
} tr[N << 2];
void pushup(int u)
{
tr[u].mx = tr[ls].mx + tr[rs].mx;
}
void pushdown(int u)
{
if (tr[u].add)
{
tr[ls].mx += tr[u].add * (tr[ls].r - tr[ls].l + 1);
tr[rs].mx += tr[u].add * (tr[rs].r - tr[rs].l + 1);
tr[ls].add += tr[u].add;
tr[rs].add += tr[u].add;
tr[u].add = 0;
}
}
void build(int u, int l, int r)
{
tr[u] = {l, r, 0, 0};
if (l == r)
{
tr[u].mx = 0;
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
pushup(u);
}
void modify(int u, int l, int r, int val)
{
if (tr[u].l >= l && tr[u].r <= r)
{
tr[u].mx += val * (tr[u].r - tr[u].l + 1);
tr[u].add += val;
return;
}
else
{
int mid = (tr[u].l + tr[u].r) >> 1;
pushdown(u);
if (mid >= l)
modify(ls, l, r, val);
if (r > mid)
modify(rs, l, r, val);
pushup(u);
}
}
ll query(int u, int l, int r)
{
if (l <= tr[u].l && tr[u].r <= r)
return tr[u].mx;
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (r <= mid)
return query(ls, l, r);
if (l > mid)
return query(rs, l, r);
return query(ls, l, r) + query(rs, l, r);
}
} t;
void solve()
{
int n, m;
cin >> n >> m;
vi a(n + 1);
rep(i, 1, n)
{
cin >> a[i];
}
vi ans(m + 1);
t.build(1, 0, m);
rep(i, 1, n)
{
int cnt = t.query(1, a[i] + 1, m);
int x = m - a[i];
ans[x] += cnt;
if (a[i] != 0)
{
cnt = t.query(1, 0, a[i] - 1);
ans[x] += cnt;
}
t.modify(1, a[i], a[i], 1);
}
t.build(1, 0, m);
rep1(i, n, 1)
{
if (a[i] != 0)
{
int cnt = t.query(1, 0, a[i] - 1);
int x = m - a[i];
ans[0] += cnt;
ans[x] -= cnt;
}
int cnt = t.query(1, a[i] + 1, m);
int x = m - a[i];
ans[x] -= cnt;
t.modify(1, a[i], a[i], 1);
}
rep(i, 0, m - 1)
{
if (i)
ans[i] += ans[i - 1];
cout << ans[i] << '\n';
}
}
当然这题考虑变化量也能做,考虑第kkk轮,会对答案产生影响的实际上只有在这一轮达到mmm然后变成000的元素,也就是初始值为m−km-km−k的元素。他们变成000之后会把原本存在的一些逆序对搞没,也就是后面比他小的元素,还会引入一些新的逆序对,也就是前面比他大的元素。注意由于变成000了,比他大和小都是很显然的,实际上只用统计前面的非000元素个数
更多推荐


所有评论(0)