省选测试37
小A的树
线段树维护点集中最远点对
-
将点按dfn序排成序列,类似于「超级钢琴」或者「异或粽子」,每个点找到与在他前面的点集中距离最远的点,将这个距离扔进堆里,每从堆中取出一个的时候,区间也被分开成两半,再加进2个值即可,这样最终堆里会有 \(n+k\) 个点,空间复杂度正确
-
线段树维护的话,就像维护联通块直径一样就好了,查一次大概是 \(log^2\) 的(线段树的log套一个树剖的(半个)log),总时间复杂度 \(O(nlog+klog^2)\)
-
另外有一点,如果序列不按dfn排序,直接按点的编号排成序列,然后一样用线段树维护,那么时间会慢一倍(可能是树剖的时候dfn不连续了,导致常数增大)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10;
int n,cnt,K,Time;
int top[maxn],dfn[maxn],Ref[maxn],head[maxn],siz[maxn],son[maxn],fa[maxn],dep[maxn];
ll dis[maxn];
struct Edge{ int to,nxt,val; }e[maxn*2];
struct Seg{ int x,y; }t[maxn*4];
struct Node{
int x,l,r,mid;
ll val;
bool operator < (const Node &B) const {
return val<B.val;
}
};
priority_queue <Node> q;
int read(int x=0,bool f=0,char ch=getchar()){
for(;ch<'0' || ch>'9';ch=getchar()) f=ch=='-';
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch&15);
return f?-x:x;
}
void add(int x,int y,int z){
e[++cnt]=(Edge){y,head[x],z};
head[x]=cnt;
}
void dfs1(int x,int prt){
siz[x]=1,fa[x]=prt,dep[x]=dep[prt]+1;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==prt) continue;
dis[y]=dis[x]+e[i].val;
dfs1(y,x);
siz[x]+=siz[y];
if(!son[x] || siz[y]>siz[son[x]]) son[x]=y;
}
}
void dfs2(int x,int tp){
top[x]=tp,dfn[x]=++Time,Ref[Time]=x;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(y!=fa[x]&&y!=son[x]) dfs2(y,y);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
ll Dis(int x,int y){
return dis[x]+dis[y]-dis[lca(x,y)]*2;
}
Seg up(const Seg &A,const Seg &B){
if(!A.x) return B;
if(!B.x) return A;
int tmp[4]={A.x,A.y,B.x,B.y};
int xx,yy;
ll mx=-1e18;
for(int i=0;i<=3;++i){
for(int j=i+1;j<=3;++j){
ll nd=Dis(tmp[i],tmp[j]);
if(nd>mx) mx=nd,xx=tmp[i],yy=tmp[j];
}
}
return (Seg){xx,yy};
}
void build(int rt,int l,int r){
if(l==r) return t[rt]=(Seg){Ref[l],Ref[l]},void();
int mid=(l+r)/2;
build(rt*2,l,mid),build(rt*2+1,mid+1,r);
t[rt]=up(t[rt*2],t[rt*2+1]);
}
Seg ask(int rt,int l,int r,int x,int y){
if(x<=l&&r<=y) return t[rt];
int mid=(l+r)/2; Seg ret=(Seg){0,0};
if(x<=mid) ret=up(ret,ask(rt*2,l,mid,x,y));
if(y>mid) ret=up(ret,ask(rt*2+1,mid+1,r,x,y));
return ret;
}
void Push(int x,int l,int r){
Seg now=ask(1,1,n,l,r);
ll d1=Dis(x,now.x),d2=Dis(x,now.y);
if(d1>d2) q.push((Node){x,l,r,dfn[now.x],d1});
else q.push((Node){x,l,r,dfn[now.y],d2});
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read(),K=read();
for(int i=1;i<n;++i){
int x=read(),y=read(),z=read();
add(x,y,z),add(y,x,z);
}
dfs1(1,0),dfs2(1,1);
build(1,1,n);
for(int i=2;i<=n;++i) Push(Ref[i],1,i-1);
for(int i=1;i<=K;++i){
int x=q.top().x;
int l=q.top().l;
int r=q.top().r;
int mid=q.top().mid;
printf("%lld\n",q.top().val);
q.pop();
if(l<mid) Push(x,l,mid-1);
if(mid<r) Push(x,mid+1,r);
}
return 0;
}
小B的序列
势能分析线段树
当进行修改操作的时候,只有当前的操作是对于区间所有数起相同效应的时候,才能统一修改,否则递归到子区间处理
具体的,‘&’的数的2进制位上的0会影响序列中数的数值,‘|’的数的二进制位上的1会造成影响,那么我们当前区间的所有数每个这样的二进制位上的数都是0或都是1的话,我们就可以直接改这一整个区间,打上标记,这个的判断以及答案的维护我们需要维护5个东西,区间max,区间and和,区间or和,and标记,or标记。
至于打标记,强制先and后or,假设我们一个区间,既有and标记,又有or标记,那么这样处理:
(A and B) or C) and D = (A and (B and D)) or (C and D)
(A and B) or C) or D = (A and B) or (C or D)
通俗来说:
-
如果要加and标记x,那么当前的and标记和or标记都要and上x
-
如果要加or标记x,那么只有当前or标记需要or上x
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
const int maxs=(1<<20)-1;
int n,q,a[maxn];
struct Node{
int sor,sand,mx,lzor,lzand;
}t[maxn*4];
int read(int x=0,bool f=0,char ch=getchar()){
for(;ch<'0' || ch>'9';ch=getchar()) f=ch=='-';
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch&15);
return f?-x:x;
}
void up(int x){
t[x].mx=max(t[x*2].mx,t[x*2+1].mx);
t[x].sor=t[x*2].sor|t[x*2+1].sor;
t[x].sand=t[x*2].sand&t[x*2+1].sand;
}
void upd(int x,int va,int vo){
t[x].mx&=va,t[x].sor&=va,t[x].sand&=va;
t[x].mx|=vo,t[x].sor|=vo,t[x].sand|=vo;
t[x].lzand&=va,t[x].lzor&=va;
t[x].lzor|=vo;
}
void down(int x){
upd(x*2,t[x].lzand,t[x].lzor);
upd(x*2+1,t[x].lzand,t[x].lzor);
t[x].lzand=maxs,t[x].lzor=0;
}
void build(int rt,int l,int r){
t[rt].lzand=maxs;
if(l==r) return t[rt]=(Node){a[l],a[l],a[l],0,maxs},void();
int mid=(l+r)/2;
build(rt*2,l,mid),build(rt*2+1,mid+1,r);
up(rt);
}
void And(int rt,int l,int r,int x,int y,int v){
if(x>y) return;
if(l==r || (x<=l&&r<=y&&(v|(t[rt].sor^t[rt].sand))==v)){
t[rt].mx&=v;
t[rt].sor&=v;
t[rt].sand&=v;
t[rt].lzand&=v;
t[rt].lzor&=v;
return;
}
int mid=(l+r)/2;
if(t[rt].lzor || t[rt].lzand!=maxs) down(rt);
if(x<=mid) And(rt*2,l,mid,x,y,v);
if(y>mid) And(rt*2+1,mid+1,r,x,y,v);
up(rt);
}
void Or(int rt,int l,int r,int x,int y,int v){
if(x>y) return;
if(l==r || (x<=l&&r<=y&&((v&(t[rt].sor^t[rt].sand))==0))){
t[rt].mx|=v;
t[rt].sor|=v;
t[rt].sand|=v;
t[rt].lzor|=v;
return;
}
int mid=(l+r)/2;
if(t[rt].lzor || t[rt].lzand!=maxs) down(rt);
if(x<=mid) Or(rt*2,l,mid,x,y,v);
if(y>mid) Or(rt*2+1,mid+1,r,x,y,v);
up(rt);
}
int Ask(int rt,int l,int r,int x,int y){
if(x<=l&&r<=y) return t[rt].mx;
int mid=(l+r)/2,ret=0;
if(t[rt].lzor || t[rt].lzand!=maxs) down(rt);
if(x<=mid) ret=max(ret,Ask(rt*2,l,mid,x,y));
if(y>mid) ret=max(ret,Ask(rt*2+1,mid+1,r,x,y));
return ret;
}
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
n=read(),q=read();
for(int i=1;i<=n;++i) a[i]=read();
build(1,1,n);
for(int i=1;i<=q;++i){
int opt=read();
if(opt==1){
int l=read(),r=read(),x=read();
And(1,1,n,l,r,x);
}
else if(opt==2){
int l=read(),r=read(),x=read();
Or(1,1,n,l,r,x);
}
else{
int l=read(),r=read();
printf("%d\n",Ask(1,1,n,l,r));
}
}
return 0;
}
小C的利是
行列式,构建k个矩阵,对于每个矩阵,使其值的指数为当前矩阵的值,从而实现将 \(\sum\) 变为 \(\prod\)
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int n,K;
int a[105][105],s[105][105],xy[105][105];
bool isp(int x){
for(int i=2;i*i<=x;++i) if(!(x%i)) return 0;
return 1;
}
int fp(int a,int x,int mod,int ans=1){
for(;x;x>>=1,a=(ll)a*a%mod)
if(x&1) ans=(ll)ans*a%mod;
return ans;
}
int det(int n,int mod){
int ans=1;
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
if(xy[j][i]){
for(int k=i;k<=n;++k) swap(xy[i][k],xy[j][k]);
ans=-ans;
break;
}
}
ans=(ll)ans*xy[i][i]%mod;
if(!ans) break;
int inv=fp((xy[i][i]%mod+mod)%mod,mod-2,mod);
for(int j=i+1;j<=n;++j){
int tmp=(ll)xy[j][i]*inv%mod;
for(int k=i;k<=n;++k) xy[j][k]=(xy[j][k]-(ll)tmp*xy[i][k]%mod)%mod;
}
}
return (ans%mod+mod)%mod;
}
int main(){
freopen("luckymoney.in","r",stdin);
freopen("luckymoney.out","w",stdout);
scanf("%d%d",&n,&K);
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) scanf("%d",&a[i][j]);
int p=K*317913+1,g,sum=0;
while(!isp(p)) p+=K;
for(g=2;g<p;++g) if(fp(g,K,p)==1) break;
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) s[i][j]=fp(i+j,j,998244353)%p;
for(int now=0,x=1;now<K;++now,x=(ll)x*g%p){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(a[i][j]!=-1) xy[i][j]=(ll)fp(x,a[i][j],p)*s[i][j]%p;
else xy[i][j]=0;
}
}
(sum+=det(n,p))%=p;
}
puts(sum?"Yes":"No");
return 0;
}