【Luogu4299】首都

BZOJ权限题。
洛谷

题目描述

在X星球上有N个国家,每个国家占据着X星球的一座城市。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。

X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。

同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。

现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理:

A x y:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。
Q x:询问当前编号为x的城市所在国家的首都。
Xor:询问当前所有国家首都编号的异或和。

Sol

就是让你动态维护重心 , 但只有加边操作。

重心的一个性质 , 当加入一个叶子节点后 , 重心最多移动一条边的距离。
而判断一个点是否是重心 , 只需要判断它的各儿子的子树大小是否超过整棵树大小一半即可。
所以有一个暴力做法 , 启发式合并 , 然后判断重心是否移动即可。
复杂度\(O(nlog^2n)\)

更加优秀的做法:
重心还有个性质 , 合并两棵树 , 新的重心一定在原来两个重心的路径上 。
那么我们用 \(LCT\) 把路径抠出来 , 重心的位置是可以二分出来的。
具体来说 , 我们在 \(Splay\) 上走 , 每次把两边的 \(size\) 维护好然后判断最大子树大小就行了
注意这里并不需要关心虚子树大小 , 简单来理解就是重心会往大的子树方向动 , 但是合并两棵子树后重心在路径上 , 所以不用关心原来的子树。严格一点分析 , 由于原来那些子树大小不超过原来大小的一半 , 自然不会超过现在大小的一半 。

code:

#include<bits/stdc++.h>
using namespace std;
template<class T>inline void init(T&x){
    x=0;char ch=getchar();bool t=0;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    if(t) x=-x;
}
const int N(1e5+10);
int n,m;
#define ls son[0]
#define rs son[1]
#define __ NULL
#define get_son(a) (a->fa->rs==a)
#define get(a,b,c) (a? a->b:c)
#define IS(a) (a&&(!a->fa||(a->fa->son[get_son(a)]!=a)))

struct node{
    node*son[2],*fa;int size,cnt;bool rev;
    node(){ls=rs=fa=__,size=cnt=1;rev=0;}
}T[N],*stk[N];int top=0;
inline void update(node*p){if(!p)return;p->size=get(p->ls,size,0)+get(p->rs,size,0)+p->cnt;}
inline void rotate(node*p){
    int k=get_son(p);node*q=p->fa,*gp=p->fa->fa;
    q->son[k]=p->son[k^1];
    if(p->son[k^1]) p->son[k^1]->fa=q;
    if(!IS(q)) gp->son[get_son(q)]=p;
    p->fa=gp,q->fa=p,p->son[k^1]=q;
    return update(q);
}
inline void push_down(node*p){
    if(!p||!p->rev)return;swap(p->ls,p->rs);p->rev=0;
    if(p->ls)p->ls->rev^=1;if(p->rs) p->rs->rev^=1;
    return;
}
inline void Push(node*p){
    stk[top=1]=p;while(!IS(p)) p=p->fa,stk[++top]=p;
    while(top) push_down(stk[top--]);return;
}
inline void Splay(node*p){
    if(!p)return;Push(p);
    for(;!IS(p);rotate(p)) if(IS(p->fa)) continue;else get_son(p->fa)==get_son(p)? rotate(p->fa):rotate(p);
    return update(p);
}
inline void access(node*p){node*pre=__;for(;p;pre=p,p=p->fa)Splay(p),p->cnt+=get(p->rs,size,0)-get(pre,size,0),p->rs=pre,update(p);}
inline void make_root(node*p){access(p);Splay(p);p->rev^=1;}
inline void split(node*p,node*q){make_root(p),access(q),Splay(q);return;}
inline void link(node*p,node*q){split(p,q);p->fa=q;q->cnt+=p->size,update(q);return;}
inline void cut(node*p,node*q){split(p,q);if(q->ls==p) p->fa=q->ls=__,update(q);return;}
inline node* Find(node*p){access(p);Splay(p);while(p->ls)p=p->ls;return p;}
inline int WP(int u){return (int)(Find(&T[u])-T);}
#define ID(a) ((a)-T)
int Xor,TOT;
inline void Solve(node*p,node*q) {
    split(p,q);node*u=q;node*Ne=__;
    int ban=TOT>>1;
    int suml=0,sumr=0;
    int nowl,nowr;
    while(u) {
        push_down(u);
        node*L=u->ls,*R=u->rs;
        nowl=suml+get(L,size,0),nowr=sumr+get(R,size,0);
        if(nowl<=ban&&nowr<=ban) {
            if(TOT&1) {Ne=u;break;}
            else if((!Ne)||ID(Ne)>ID(u)) Ne=u;
        }
        if(nowl>=nowr) sumr+=get(R,size,0)+u->cnt,u=L;
        else           suml+=get(L,size,0)+u->cnt,u=R;
    }
    make_root(Ne);Xor^=ID(Ne);
}
int main()
{
    init(n),init(m);
    int u,v;for(int i=1;i<=n;++i) Xor^=i;
    for(int i=1;i<=m;++i) {
        char ch=getchar();while(ch!='A'&&ch!='Q'&&ch!='X') ch=getchar();
        if(ch=='A') {
            init(u),init(v);
            int x=WP(u),y=WP(v);
            Splay(&T[x]),Splay(&T[y]);
            TOT=T[x].size+T[y].size;
            Xor^=x^y;link(&T[u],&T[v]);
            Solve(&T[x],&T[y]);
        }
        else if(ch=='Q') {init(u);printf("%d\n",WP(u));}
        else if(ch=='X') printf("%d\n",Xor);
    }
}

转载于:https://www.cnblogs.com/NeosKnight/p/10428604.html

资源下载链接为: https://pan.quark.cn/s/9e7ef05254f8 行列式是线性代数的核心概念,在求解线性方程组、分析矩阵特性以及几何计算中都极为关键。本教程将讲解如何用C++实现行列式的计算,重点在于如何输出分数形式的结果。 行列式定义如下:对于n阶方阵A=(a_ij),其行列式由主对角线元素的乘积,按行或列的奇偶性赋予正负号后求和得到,记作det(A)。例如,2×2矩阵的行列式为det(A)=a11×a22-a12×a21,而更高阶矩阵的行列式可通过Laplace展开或Sarrus规则递归计算。 在C++中实现行列式计算时,首先需定义矩阵类或结构体,用二维数组存储矩阵元素,并实现初始化、加法、乘法、转置等操作。为支持分数形式输出,需引入分数类,包含分子和分母两个整数,并提供与整数、浮点数的转换以及加、减、乘、除等运算。C++中可借助std::pair表示分数,或自定义结构体并重载运算符。 计算行列式的函数实现上,3×3及以下矩阵可直接按定义计算,更大矩阵可采用Laplace展开或高斯 - 约旦消元法。Laplace展开是沿某行或列展开,将矩阵分解为多个小矩阵的行列式乘积,再递归计算。在处理分数输出时,需注意避免无限循环和除零错误,如在分数运算前先约简,确保分子分母互质,且所有计算基于整数进行,最后再转为浮点数,以避免浮点数误差。 为提升代码可读性和可维护性,建议采用面向对象编程,将矩阵类和分数类封装,每个类有明确功能和接口,便于后续扩展如矩阵求逆、计算特征值等功能。 总结C++实现行列式计算的关键步骤:一是定义矩阵类和分数类;二是实现矩阵基本操作;三是设计行列式计算函数;四是用分数类处理精确计算;五是编写测试用例验证程序正确性。通过这些步骤,可构建一个高效准确的行列式计算程序,支持分数形式计算,为C++编程和线性代数应用奠定基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值