[模拟赛]模拟赛集合。
#A1
A 卡利班
发现其任意时刻其结构为一颗树,而 \(f(x)\) 即为这颗树的直径 \((a,b)\) 两端点到 \(x\) 的最大值。
我们要做的就是动态合并两颗树。
可以使用 \(LCT\) ,复杂度 \(O(nlogn)\)。
如果不会 \(LCT\) 的话建议使用启发式合并,暴力遍历小的那边,暴力更新\(dep\)和倍增数组。
A 卡利班
#include<iostream>
#include<cstdio>
#define ll long long
#define N 3000005
//___________________
ll f[N],c[N][2],siz[N],st[N];
bool ri[N];
#define l(x) c[x][0]
#define r(x) c[x][1]
inline bool nroot(int x){return l(f[x]) == x || r(f[x]) == x;}
inline void up(int x){siz[x] = (siz[l(x)] + siz[r(x)] + 1);}
inline void pushr(int x){std::swap(l(x),r(x));ri[x] ^= 1;}
inline void pushdown(int x){
if(ri[x]){
if(l(x))pushr(l(x));
if(r(x))pushr(r(x));
ri[x] = 0;
}
}
inline void rotate(int x){
int y = f[x],z = f[y],k = r(y) == x,w = c[x][!k];
if(nroot(y))
c[z][r(z) == y] = x;c[x][!k] = y;c[y][k] = w;
if(w)
f[w] = y;f[y] = x;f[x] = z;
up(y);up(x);
}
inline void splay(int x){
ll y = x,z = 0;
st[++z] = y;
while(nroot(y))st[++z] = y = f[y];
while(z)pushdown(st[z -- ]);
while(nroot(x)){
ll y = f[x],z = f[y];
if(nroot(y))
rotate((l(y) == x) ^ (l(z) == y) ? x : y);
rotate(x);
}
up(x);
}
inline void access(int x){
for(int y = 0;x;x = f[y = x])
splay(x),r(x) = y,up(x);
}
inline void makeroot(int x){access(x),splay(x),pushr(x);}
inline void split(int x,int y){makeroot(x),access(y),splay(y);}
inline void link(int x,int y){makeroot(x);f[x] = y;}
//__________________LCT
ll fa[N],l[N],r[N];
inline ll find(int x){return (fa[x] == x) ? x : fa[x] = find(fa[x]);}
inline void merge(int x,int y){//x -> y
ll ans = 0,ansl,ansr;
fa[x] = y;
split(l[x],r[x]);
if(siz[r[x]] - 1 > ans)
ans = siz[r[x]] - 1,ansl = l[x],ansr = r[x];
split(l[y],r[x]);
if(siz[r[x]] - 1 > ans)
ans = siz[r[x]] - 1,ansl = l[y],ansr = r[x];
split(l[x],r[y]);
if(siz[r[y]] - 1 > ans)
ans = siz[r[y]] - 1,ansl = l[x],ansr = r[y];
split(l[y],r[y]);
if(siz[r[y]] - 1 > ans)
ans = siz[r[y]] - 1,ansl = l[y],ansr = r[y];
split(l[x],l[y]);
if(siz[l[y]] - 1 > ans)
ans = siz[l[y]] - 1,ansl = l[x],ansr = l[y];
split(r[x],r[y]);
if(siz[r[y]] - 1 > ans)
ans = siz[r[y]] - 1,ansl = r[x],ansr = r[y];
l[y] = ansl,r[y] = ansr;
// std::cout<<x<<" "<<y<<" "<<l[y]<<" "<<r[y]<<std::endl;
}
//__________________DSU
ll n,q,type,last;
int main(){
// freopen("A.in","r",stdin);
// freopen("A.out","w",stdout);
scanf("%lld",&type);
scanf("%lld%lld",&n,&q);
for(int i = 1;i <= n;++i)
fa[i] = l[i] = r[i] = i;
while(q -- ){
ll opt,u,v;
scanf("%lld",&opt);
if(opt == 1){
scanf("%lld%lld",&u,&v);
u ^= type * last;
v ^= type * last;
link(u,v);
merge(find(u),find(v));
}else{
scanf("%lld",&u);
u ^= type * last;
ll al = l[find(u)],ar = r[find(u)];
ll ans = 0;
// std::cout<<al<<" "<<ar<<std::endl;
split(al,u);
ans = std::max(ans,siz[u] - 1);
split(ar,u);
ans = std::max(ans,siz[u] - 1);
std::cout<<(last = ans)<<std::endl;
}
}
}
B. M-IV-Y
对所有颜色的位置维护其最前,最后两个位置,并分别处理,然后静态答案为\(A\)的数在\(B\)中的逆序对数。
考虑把一个操作看做从一个颜色集合中删除一个数,然后加入一个颜色集合。
然后可能对颜色集合的最前最后产生影响,所以又变成了从\(A,B\)中加入,删除点。
考虑有修改操作的二维数点,CDQ分治。
C. 博福斯 TRV
不会可持久平衡树。
暴力六十不好吗。
#A3
A. 搞颜色
考虑拆贡献到每个颜色。
对每两个点夹住的区间,根据其长度进行一个区间等差数列加,用两颗BIT维护。
A. 搞颜色
#include<bits/stdc++.h>
#define ll long long
#define N 300005
int n,m;
int a[N];
inline int read(){
int ans = 0,f = 1;
char a = getchar();
while(a != '-' && !(a <= '9' && a >= '0'))
a = getchar();
while(a <= '9' && a >= '0')
ans = (ans << 1) + (ans << 3) + (a - '0'),a = getchar();
return f * ans;
}
std::set<int>S[N];
ll T[N];//BIT
ll B[N];//Tag
#define lowbit(x) (x & -x)
inline void addT(int x,int p){
for(int i = x;i <= n;i += lowbit(i))
T[i] += p;
}
inline void addB(int x,int p){
for(int i = x;i <= n;i += lowbit(i))
B[i] += p;
}
inline ll QT(int x){
ll ans = 0;
for(int i = x;i;i -= lowbit(i))
ans = ans + T[i];
return ans;
}
inline int QB(int x){
int ans = 0;
for(int i = x;i;i -= lowbit(i))
ans = ans + B[i];
return ans;
}
#define IT std::set<int>::iterator
inline void Add(int len,int opt){//长度,符号
addT(1,len * opt);
addT(len + 1,-1 * len * opt);
addB(1,1 * opt);
addB(len + 1,-1 * opt);
}
inline void pre(){//first_ans
for(int i = 1;i <= n;++i){
// std::cout<<i<<" "<<std::endl;
IT it = S[i].begin();
++ it;
IT end = S[i].end();
while(it != S[i].end()){
IT las = prev(it);
// std::cout<<*las<<" "<<*it<<" "<<(*it - *las - 1)<<std::endl;
Add(*it - *las - 1,1);
++ it;
}
}
}
inline void change(int x,int w){
IT it = S[a[x]].lower_bound(x);
IT las,nex;
nex = ++it;
-- it;
las = prev(it);
Add(*it - *las - 1,-1);
Add(*nex - *it - 1,-1);
Add(*nex - *las - 1,1);
S[a[x]].erase(x);
a[x] = w;
S[a[x]].insert(x);
it = S[a[x]].lower_bound(x);
nex = ++it;
-- it;
las = prev(it);
Add(*it - *las - 1,1);
Add(*nex - *it - 1,1);
Add(*nex - *las - 1,-1);
}
int main(){
// freopen("q.in","r",stdin);
// freopen("q.out","w",stdout);
n = read(),m = read();
for(int i = 1;i <= n;++i){
a[i] = read();
S[a[i]].insert(i);
}
for(int i = 1;i <= n;++i){
S[i].insert(0);
S[i].insert(n + 1);
}
pre();
while(m -- ){
int opt,x,w;
opt = read();
if(opt == 2){
x = read();
std::cout<<1ll * (n - x + 1) * n - (QT(x) - 1ll * QB(x) * (x - 1))<<std::endl;
}
else{
x = read(),w = read();
change(x,w);
}
}
}
B. 匹配
remake成功了。
高消一下,发现设\(f_i = a_{i,i}\),所以第\(i\)行,只有\(f_i\)或\(0\)两个取值。
当且仅当\(j \in i\),\(a_{i,j} = f_i\)
所以\(\sum_{k\in i}f_k = a_i\)
考虑归纳法证明其,考虑第\(i\)行减去所有子集\(k\)对应的行消元结果,\(\sum_{k\in i}f_k[k\in j] = \sum_{k \in (i \& j)}f_k(k \neq i)\)
若\(i \notin j\),则原式\(\sum_{k\in i\& j}f_k = a_{i \& j}\)
否则\(\sum_{k \in i} f_k = a_i - f_i(i \neq k)\)
考虑\(\sum_{k \in i}f_k = a_i\),可以推出\(a\)是\(f\)高维前缀和的结果,做一次高维差分,一次FMT可以得到答案。
复杂度\(O(nlogn)\).
B. 匹配
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
#define N 1000005
ll ans = 1;
ll a[N];
int n;
inline void FWT_or(ll *f,int type){
for(int mid = 1;mid < n;mid <<= 1){
for(int block = mid << 1,j = 0;j < n;j += block)
for(int i = j;i < j + mid;++i)
f[i + mid] = (f[i + mid] + f[i] * type + mod) % mod;
}
}
int main(){
// freopen("q.in","r",stdin);
// freopen("q.out","w",stdout);
scanf("%d",&n);
for(int i = 0;i < n;++i)
scanf("%lld",&a[i]);
FWT_or(a,-1);
for(int i = 0;i < n;++i)
ans = ans * a[i] % mod;
std::cout<<ans<<std::endl;
}
C. 蹦蹦炸弹
投出带来无限快乐的蹦蹦炸弹!
考虑我们使用\(krasual\)。
我们设\(a_i\)为\(i\)的联通块编号。
考虑如果有连边则\([l1,r1],[l2,r2]\)有不同。
hash,二分直接找到要改的地方。
每次都会加一条边,只会加\(n - 1\)条。
然后考虑更新联通块编号。
即我们启发式合并暴力修改一边的编号。
是傻逼题,可惜我也是傻逼。
C. 蹦蹦炸弹
#include<bits/stdc++.h>
#define ll long long
#define N 100005
#define mod 998244353
#define M 500005
int n,m;
int fa[N];
std::set<int>S[N];
ll base[N],inv[N];
inline ll pow(ll a,ll b){
ll ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll T[N];
#define lowbit(x) (x & -x)
inline void add(int x,ll p){
for(int i = x;i <= n;i += lowbit(i))
T[i] = (T[i] + p + mod) % mod;
}
inline ll find(int x){
ll ans = 0;
for(int i = x;i;i -= lowbit(i))
ans = (ans + T[i]) % mod;
return ans;
}
inline ll Hash(int l,int r){
return (find(r) - find(l - 1) + mod) % mod * inv[l] % mod;
}
struct P{
int l1,l2,len,w;
}e[M];
bool operator < (P a,P b){
return a.w < b.w;
}
ll ans = 0;
int cnt = 0;
#define IT std::set<int>::iterator
inline void merge(int x,int y){
if(S[x].size() > S[y].size())std::swap(x,y);//x -> y
IT it = S[x].begin();
while(it != S[x].end()){
add(*it,-1 * base[*it] * x % mod);
add(*it,base[*it] * y % mod);
fa[*it] = y;
S[y].insert(*it);
++it;
}
}
inline bool check(int l1,int r1,int l2,int r2){
// std::cout<<"["<<l1<<" "<<r1<<"]"<<"["<<l2<<" "<<r2<<"]"<<std::endl;
// std::cout<<Hash(l1,r1)<<" "<<Hash(l2,r2)<<std::endl;
if(Hash(l1,r1) == Hash(l2,r2) || cnt == n - 1)
return 0;
#define mid1 ((l1 + r1) >> 1)
#define mid2 ((l2 + r2) >> 1)
while(l1 != r1){
if(Hash(l1,mid1) == Hash(l2,mid2))
l1 = mid1 + 1,l2 = mid2 + 1;
else
r1 = mid1,r2 = mid2;
// std::cout<<"["<<l1<<" "<<r1<<"]"<<"["<<l2<<" "<<r2<<"]"<<std::endl;
}
// std::cout<<"ADD"<<"["<<l1<<" "<<r1<<"]"<<"["<<l2<<" "<<r2<<"]"<<std::endl;
merge(fa[l1],fa[l2]);
return 1;
}
int main(){
// freopen("q.in","r",stdin);
// freopen("q.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)
fa[i] = i,S[i].insert(i);
base[0] = 1;
for(int i = 1;i <= n;++i)
base[i] = base[i - 1] * 10 % mod;
for(int i = 1;i <= n;++i)
add(i,fa[i] * base[i] % mod);
inv[n] = pow(base[n],mod - 2);
for(int i = n - 1;i >= 0;--i)
inv[i] = inv[i + 1] * 10 % mod;
for(int i = 1;i <= m;++i){
scanf("%d%d%d%d",&e[i].l1,&e[i].l2,&e[i].len,&e[i].w);
}
std::sort(e + 1,e + m + 1);
for(int i = 1;i <= m;++i){
// std::cout<<e[i].l1<<" "<<e[i].l2<<" "<<e[i].len<<" "<<e[i].w<<std::endl;
// check(e[i].l1,e[i].l1 + e[i].len - 1,e[i].l2,e[i].l2 + e[i].len - 1);
while(check(e[i].l1,e[i].l1 + e[i].len - 1,e[i].l2,e[i].l2 + e[i].len - 1))
ans += e[i].w,cnt ++ ;
}
std::cout<<ans<<std::endl;
}
/*
5 3
1 3 2 5
2 3 2 3
4 5 1 1
*/