做题记录3.28
模拟赛
T1 发光虫
经典trick,二分答案,然后2-SAT判断是否合法,可以做到 $ O(n^2\log n) $
考虑优化建图,可以使用分块或可持久化线段树。
这里详细揭秘线段树做法,将原序列按照一个关键字排序,建初始的线段树,对于每一种颜色在原树上删掉,这里要用可持久化,查询时二分左右端点,在线段树上区间查询,把覆盖到的区间用新建节点合并起来就行。复杂度 $ O(n \log V \log n) $
怎么我常数这么大啊,都跑不过分块。
CODE
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+100;
int n,m,tot,q[N],h[N];
int fi[N],gi[N];
struct node{
int x,y,k;
}ins[N];
vector<int>sub[N];
struct wxr{
int id,val;
}f[N],g[N];
bool cmp(wxr fir,wxr sec){
return fir.val<sec.val;
}
#define fir(x) ((x<<1)-1)
#define sec(x) (x<<1)
struct graph{
int cnt,head[N*400],to[N*1600],nxt[N*1600];
void add(int u,int v){
assert(cnt<N*1600);
cnt++;
to[cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void Clear(){
memset(head,0,sizeof(head));
for(int i=1;i<=cnt;i++)to[i]=nxt[i]=0;
cnt=0;
}
}e;
int dfn[N*400],low[N*400],scc[N*400],now,s;
bool vis[N*400];
int st[N*400],tp;
void Tarjan(int u){
dfn[u]=low[u]=++now;
vis[u]=1;st[++tp]=u;
for(int i=e.head[u];i;i=e.nxt[i]){
int v=e.to[i];
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])low[u]=min(low[u],low[v]);
}
if(low[u]>=dfn[u]){
int k=0;s++;
do{
k=st[tp];tp--;
vis[k]=0;
scc[k]=s;
}while(k!=u);
}
}
bool check(){
memset(dfn,0,sizeof(int)*(now+1));
memset(low,0,sizeof(int)*(now+1));
memset(scc,0,sizeof(int)*(now+1));
now=s=0;
for(int i=1;i<=tot*2;i++){
if(!dfn[i])Tarjan(i);
}
for(int i=1;i<=tot;i++){
if(scc[fir(i)]==scc[sec(i)])return 0;
}
return 1;
}
int root[2][N];
struct seg{
int cnt,ls[N*100],rs[N*100],id[N*100];
void Clear(){
memset(ls,0,sizeof(int)*(cnt+1));
memset(rs,0,sizeof(int)*(cnt+1));
memset(id,0,sizeof(int)*(cnt+1));
cnt=0;
}
inline void up(int rt){
if(ls[rt]){
e.add(fir(id[rt]),fir(id[ls[rt]]));e.add(sec(id[rt]),sec(id[ls[rt]]));
}
if(rs[rt]){
e.add(fir(id[rt]),fir(id[rs[rt]]));e.add(sec(id[rt]),sec(id[rs[rt]]));
}
}
void build(int &rt,int l,int r,bool flag){
rt=++cnt;
if(l==r){
if(flag)id[rt]=h[g[l].id];
else id[rt]=q[f[l].id];
return ;
}
id[rt]=++tot;
int mid=(l+r)>>1;
build(ls[rt],l,mid,flag);
build(rs[rt],mid+1,r,flag);
up(rt);
}
void updata(int &rt,int l,int r,int x){
if(l==r)return rt=0,void();
assert(cnt<N*200);
++cnt;ls[cnt]=ls[rt];rs[cnt]=rs[rt];
id[rt=cnt]=++tot;
int mid=(l+r)>>1;
if(mid>=x)updata(ls[rt],l,mid,x);
else updata(rs[rt],mid+1,r,x);
up(rt);
}
int query(int rt,int l,int r,int L,int R){
if(L>R)return 0;
if(!rt)return 0;
if(L<=l&&r<=R)return rt;
int res=++cnt,mid=(l+r)>>1;id[cnt]=++tot;
if(mid>=L)ls[res]=query(ls[rt],l,mid,L,R);
if(mid+1<=R)rs[res]=query(rs[rt],mid+1,r,L,R);
up(res);
return res;
}
}t1,t2;
int bs1(wxr *x,int y){
int l=1,r=n+1;
while(l<r){
int mid=(l+r)>>1;
if(x[mid].val>y)r=mid;
else l=mid+1;
}
return l;
}
int bs2(wxr *x,int y){
int l=0,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(x[mid].val<y)l=mid;
else r=mid-1;
}
return l;
}
bool solve(int maxn){
e.Clear();tot=n*2;
for(int i=1;i<=n;i++){
e.add(fir(q[i]),sec(h[i]));e.add(sec(h[i]),fir(q[i]));
e.add(sec(q[i]),fir(h[i]));e.add(fir(h[i]),sec(q[i]));
}
t1.Clear();t2.Clear();
t1.build(root[0][0],1,n,0);t2.build(root[1][0],1,n,1);
for(int i=1;i<=n;i++){
if(sub[i].empty())continue;
root[0][i]=root[0][0];root[1][i]=root[1][0];
for(int j:sub[i]){
t1.updata(root[0][i],1,n,fi[j]);
t2.updata(root[1][i],1,n,gi[j]);
}
for(int j:sub[i]){
int k=t1.query(root[0][i],1,n,bs1(f,ins[j].x-maxn),bs2(f,ins[j].x+maxn));
if(t1.id[k])e.add(fir(q[j]),sec(t1.id[k]));
k=t2.query(root[1][i],1,n,bs1(g,ins[j].x-maxn),bs2(g,ins[j].x+maxn));
if(t2.id[k])e.add(fir(q[j]),sec(t2.id[k]));
k=t1.query(root[0][i],1,n,bs1(f,ins[j].y-maxn),bs2(f,ins[j].y+maxn));
if(t1.id[k])e.add(fir(h[j]),sec(t1.id[k]));
k=t2.query(root[1][i],1,n,bs1(g,ins[j].y-maxn),bs2(g,ins[j].y+maxn));
if(t2.id[k])e.add(fir(h[j]),sec(t2.id[k]));
}
}
return check();
}
signed main()
{
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&ins[i].x,&ins[i].y,&ins[i].k);
f[i]={i,ins[i].x};
g[i]={i,ins[i].y};
q[i]=++tot;h[i]=++tot;
sub[ins[i].k].push_back(i);
}
sort(f+1,f+n+1,cmp);sort(g+1,g+n+1,cmp);
for(int i=1;i<=n;i++)fi[f[i].id]=i,gi[g[i].id]=i;
int l=0,r=2000000000;
while(l<r){
int mid=(l+r+1)>>1;
if(solve(mid))l=mid;
else r=mid-1;
}
cout<<l;
}
T2 跑步
赛事觉得这不虚树板子吗,怒调3h喜提5pts。
建虚树是显然的,对于虚树上的每个关键点,可以求出距离它最近的打卡点。
在原树上记录以下几个数据:
$ siz[u] $ :以 \(u\) 为根的子树权值和
$ sum[u] $ :以 \(u\) 为根的子树所有节点到达节点 \(u\) 所需要的代价和
$ h[i][u] $ :与 $ sum $ 类似,用ST表维护以 \(u\) 为底,长度为 $ 2^i $ 的一条链
对于每次查询建出新的虚树,对 $ sum,siz $ 进行一次树上差分,这样贡献分为两部分:关键点和非关键点。
前者是容易的,先让不在关键边上的非关键点跑到当前关键点 \(u\) ,贡献为差分后的 $ sum[u] $ ,然后再跑到最近的打卡点,贡献为 $ Dis \times siz[u] $ (差分后的)。
后者相对困难一些。考虑有关建边相连的两个关键点 $ x,y $ ,统计它们之间的非关键点的贡献。记录 $ fx,fy为距离x,y最近的打卡点 $ ,暂时假设 $ fx!=fy $ ,在这条链上从 \(v\) 倍增往上跳父亲,设 $ k $ 为链上第一个满足 $ Dis(k,fx) <= Dis(k,fy) $ 的非关键点,考虑先让 \(k\) 子树的所有节点跑到 \(k\) ,再从 \(k\) 跑到打卡点,贡献为 $ sum[k] + Dis(k,fy) \times siz[k] $ ,这样额外的贡献有两部分:1. 以 \(y\) 为根的子树不需要参与此过程;2. 这条链上的节点本来可以直接跑到打卡点,这里多计算了二倍到 \(k\) 的贡献,倍增时利用 \(h\) 数组容斥掉就可以了。从 \(k\) 到 \(x\) 类似。注意具体的实现细节和处理 $ fx=fy $ 的情况。
复杂度 $ O(n \log n) $ 。
为什么我的常数又这么大,用 $ O(1) $ 求LCA才能过,还跑不过两只 $ \log $
CODE
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+100;
const int inf=1e9;
int T,n,m,s[N],num,t[N];
int fa[20][N],dis[N],dep[N],st[20][N],lg[N];
ll siz[N],h[20][N];
ll ans;
ll sum[N],wi[N];
int dfn[N],tot,out[N];
bool vis[N];
struct graph{
int cnt,head[N],to[N<<1],nxt[N<<1],val[N<<1];
void add(int u,int v,int w){
cnt++;
to[cnt]=v;
nxt[cnt]=head[u];
val[cnt]=w;
head[u]=cnt;
}
void Clear(){
for(int i=1;i<=cnt;i++)head[to[i]]=0;
cnt=0;
}
}g,w;
void dfs(int u,int pa){
siz[u]=s[u];
dfn[u]=++tot;st[0][tot]=u;
for(int i=g.head[u];i;i=g.nxt[i]){
int v=g.to[i];
if(v==pa)continue;
dis[v]=dis[u]+g.val[i];dep[v]=dep[u]+1;
fa[0][v]=u;wi[v]=g.val[i];
dfs(v,u);
siz[u]+=siz[v];
sum[u]+=sum[v]+1ll*siz[v]*g.val[i];
h[0][v]=1ll*siz[v]*g.val[i];
}
out[u]=tot;
}
inline int Min(int x,int y){
if(dep[x]<dep[y])return x;
else return y;
}
inline int LCA(int x,int y){
if(x==y)return x;
x=dfn[x],y=dfn[y];
if(x>y)swap(x,y);
int llg=lg[y-x];
return fa[0][Min(st[llg][x+1],st[llg][y-(1<<llg)+1])];
}
inline int Dis(int x,int y){
if(x==0||y==0)return inf;
return dis[x]+dis[y]-dis[LCA(x,y)]*2;
}
bool cmp(int fir,int sec){
return dfn[fir]<dfn[sec];
}
void build(){
w.Clear();
sort(t+1,t+num+1,cmp);
int las=num;
for(int i=2;i<=num;i++){
int k=LCA(t[i],t[i-1]);
if(k!=t[i-1]){
if(t[las]!=k)t[++las]=k;
}
}
sort(t+1,t+las+1,cmp);
las=unique(t+1,t+las+1)-t-1;
for(int i=2;i<=las;i++){
int k=LCA(t[i-1],t[i]);
w.add(t[i],k,dis[t[i]]-dis[k]);
w.add(k,t[i],dis[t[i]]-dis[k]);
}
}
inline int get(int x,int y){
for(int i=18;i>=0;i--){
if(dep[fa[i][y]]>dep[x])y=fa[i][y];
}
return y;
}
int minn[N];
void init(int u,int f){
if(vis[u])minn[u]=u;
for(int i=w.head[u];i;i=w.nxt[i]){
int v=w.to[i];
if(v==f)continue;
int k=get(u,v);
sum[u]-=(sum[k]+1ll*siz[k]*wi[k]);
siz[u]-=siz[k];
init(v,u);
if(Dis(minn[v],u)<Dis(minn[u],u))minn[u]=minn[v];
}
}
void init2(int u,int f){
if(vis[u])minn[u]=u;
for(int i=w.head[u];i;i=w.nxt[i]){
int v=w.to[i];
if(v==f)continue;
if(Dis(minn[v],v)>Dis(minn[u],v))minn[v]=minn[u];
init2(v,u);
}
}
void solve1(int u,int pa){
ans+=sum[u]+1ll*Dis(u,minn[u])*siz[u];
for(int i=w.head[u];i;i=w.nxt[i]){
int v=w.to[i];
if(v==pa)continue;
solve1(v,u);
}
}
void Back(int u,int pa){
for(int i=w.head[u];i;i=w.nxt[i]){
int v=w.to[i];
if(v==pa)continue;
Back(v,u);
int k=get(u,v);
siz[u]+=siz[k];
sum[u]+=(sum[k]+1ll*siz[k]*wi[k]);
}
}
ll query(int x,int y,int fx,int fy){
int bx=x,by=y,bz=y;
ll dx=Dis(fx,x),dy=Dis(fy,y),res=0;
y=fa[0][y];
if(y==x)return 0;
if(dfn[y]<=dfn[fy]&&out[fy]<=out[y]&&Dis(y,fy)<=Dis(y,fx)){
for(int i=18;i>=0;i--){
if(dep[fa[i][y]]>dep[bx]){
if(dx+dis[fa[i][y]]-dis[bx]>=dy+dis[by]-dis[fa[i][y]]){
res-=h[i][y]*2;
y=fa[i][y];
}
}
}
res+=1ll*siz[by]*(dis[fa[0][by]]-dis[y])*2;
res+=sum[y]-sum[by]-1ll*siz[by]*(dis[by]-dis[y])+1ll*(siz[y]-siz[by])*Dis(fy,y);
bz=y;
y=fa[0][y];
}
for(int i=18;i>=0;i--){
if(dep[fa[i][y]]>dep[bx]){
y=fa[i][y];
}
}
if(dep[y]>dep[x])
res+=sum[y]-sum[bz]-1ll*siz[bz]*(dis[bz]-dis[y])+1ll*(siz[y]-siz[bz])*Dis(y,fx);
return res;
}
void solve2(int u,int pa){
for(int i=w.head[u];i;i=w.nxt[i]){
int v=w.to[i];
if(v==pa)continue;
solve2(v,u);
ll k=query(u,v,minn[u],minn[v]);
ans+=k;
}
}
signed main()
{
freopen("runplus.in","r",stdin);
freopen("runplus.out","w",stdout);
scanf("%d",&T);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g.add(a,b,c);g.add(b,a,c);
}
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
dis[1]=1;dep[1]=1;
dfs(1,0);
for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=lg[n];i++){
for(int j=1;j+(1<<i)-1<=n;j++){
st[i][j]=Min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
}
for(int i=1;i<=18;i++){
for(int j=1;j<=n;j++){
fa[i][j]=fa[i-1][fa[i-1][j]];
h[i][j]=h[i-1][j]+h[i-1][fa[i-1][j]];
}
}
while(m--){
scanf("%d",&num);
if(num==0)continue;
bool flag=0;
ans%=n;
for(int i=1;i<=num;i++){
scanf("%d",&t[i]);
t[i]^=ans;
if(t[i]==1)flag=1;
vis[t[i]]=1;
}
if(!flag)t[++num]=1;
ans=0;
build();
init(1,0);init2(1,0);
solve1(1,0);
Back(1,0);
solve2(1,0);
printf("%lld\n",ans);
for(int i=1;t[i];i++)minn[t[i]]=vis[t[i]]=0,t[i]=0;
}
}
T3 梦
长链剖分优化DP,这辈子有了。
设 $ f[u][i] $ 为 \(u\) 的子树中,与 \(u\) 相对深度为 \(i\) 的节点的 \(b\) 的最大值,同理,设 $ g[u][i] $ 为 \(u\) 的子树中,与 \(u\) 相对深度为 \(i\) 的节点的 \(c\) 的最大值。
然后是一个类似于dsu on tree的过程统计答案。
现在问题在于怎么快速求f,g。引入长链剖分,与重剖类似,只是重儿子是深度最大的那个,重儿子继承,轻儿子合并,注意到每条长链只会统计一次贡献,这部分均摊 $ O(n) $ ,精细化实现可以做到 $ O(nk) $ 。其实还可以更优秀,现在合并是 $ O(n) $ 的,瓶颈在于统计答案,考虑平衡二者复杂度,因为统计答案是区间查最大值,用一个类似于ST表的东西动态维护即可,复杂度 $ O(n \log k) $ 。好像单调队列可以直接 $ O(n) $ ?

浙公网安备 33010602011771号