0814 训练

梦熊苍穹第 31 场。Good Round。

做出了 T1/T2,T2 做法似乎有点憨……

T1

给定环上的若干个区间,两个点连边当且仅当对应的区间有交,求该图最大团。

考虑如果是链上的若干个区间,答案很明显是覆盖次数最大的那个点。

考虑断环成链,随便选一个点将环破开,拎出所有经过该点的区间。那么由上述结论,最大团中所有没有经过该点的区间应该有一个公共点。

枚举公共点,发现经过破开点的所有区间肯定不互相冲突,而经过公共点的所有区间也不互相冲突。所以这是二分图最大独立集问题。

二分图的连边方式是二维偏序流,直接上贪心流可以做到 O(nlogn) 求最大流。

综上复杂度为 O(n2logn)

#include <set>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int N=2003;
const int T=1e6;
int n,m,k;
struct node{
int x,y;
friend bool operator<(const node a,const node b){
return a.x<b.x;
}
}s[N],t[N];
vector<int> ins[T],del[T];
bool vis[N];
void solve(){
n=read();m=k=0;
for(int i=1;i<=n;++i){
int l=read(),r=read();
if(r==T){s[++m]=(node){0,l};continue;}
if(l>r){s[++m]=(node){r,l};continue;}
t[++k]=(node){l,r};
}
int res=0;
sort(s+1,s+m+1);sort(t+1,t+k+1);
for(int i=1;i<=k;++i){
ins[t[i].x].emplace_back(i);
del[t[i].y+1].emplace_back(i);
}
int num=0;
for(int it=1;it<T;++it){
if(ins[it].empty()&&del[it].empty()) continue;
for(int x:ins[it]) vis[x]=1,++num;
for(int x:del[it]) vis[x]=0,--num;
multiset<int> st;
int flow=0;
for(int i=1,p=1;i<=k;++i) if(vis[i]){
while(p<=m&&s[p].x<t[i].x) st.emplace(s[p++].y);
auto it=st.upper_bound(t[i].y);
if(it!=st.end()) ++flow,st.erase(it);
}
res=max(res,num-flow);
}
for(int i=1;i<=T;++i) ins[i].clear(),del[i].clear();
printf("%d\n",res+m);
}
int main(){
freopen("circle.in","r",stdin);
freopen("circle.out","w",stdout);
int tc=read();
while(tc--) solve();
return 0;
}

T2

定义如果一个字符串 t 能完整覆盖 s,即 s 中的每一个下标都至少被 t 的一次出现包含,那么称 tscover,求每个前缀的 cover 长度异或和,强制在线。

考虑到 cover 一定是 border,我们猜测 cover 是否有跟 border 一样的性质。

首先明显 covercovercover。其次如果一个字符串有两个不同的 cover,首先我们可以立马得到它们有 border 关系,既然是 border 我们就可以立马得出截取大的 cover 覆盖原串方案的前缀,加上 border 的两次出现其一定可以被小的覆盖的。

那么这样我们就知道了 cover 的偏序关系构成一颗树,这棵树是 border 树的虚树。

现在考虑只需要建出 cover 树就做完了,即要找最大 cover

我们考虑在 border 树上跳到第一个是 cover 的位置。考虑用 border 性质优化。一个观察是所有的大于一半的 border 都是 cover,这代表着将 coveri=borderi 的树边建出来,那么任何一个点到根都只需要经过 O(logn)coveriborderi 的边。

cover 性在 cover 树上有传递性,也就是你跳 border 树时,你可以在 coveri=borderi 的直链上二分出最大的 cover。用 LCT 维护每个 cover 的最后一次出现再二分可以做到两只 log。我场上写的这个做法,直接过了!

更好一点的做法是考虑继续利用 coveri=borderi 的链的性质,更新最后一次出现时直接在上面跳更新链顶,然后 LCT 上二分解决最后一条链上的情况。复杂度来到了 O(nlogn)

或者像 zhy 一样更好的想法,注意到大于一半的 border 构成等差数列,也就是说所有大 border 的树边组成的是若干条链!这是一个天然的树剖结构,免去了 LCT 的麻烦。

#include <cstdio>
#define IL inline
using namespace std;
const int N=1000003;
namespace LCT{
int ch[N][2],fa[N];
IL bool nrt(int p){return ch[fa[p]][0]==p||ch[fa[p]][1]==p;}
IL bool dir(int p){return ch[fa[p]][1]==p;}
IL void con(int x,int y,bool d){ch[x][d]=y;fa[y]=x;}
IL void rotate(int p){
int f=fa[p];bool d=dir(p),df=dir(f);
if(nrt(f)) ch[fa[f]][df]=p;
fa[p]=fa[f];
con(f,ch[p][d^1],d);
con(p,f,d^1);
}
IL void splay(int p){
while(nrt(p)){
int f=fa[p];
if(nrt(f)) rotate((dir(f)^dir(p))?p:f);
rotate(p);
}
}
IL int access(int p){int t=0;for(;p;p=fa[t=p]) splay(p),ch[p][1]=t;return t;}
IL void add(int p,int v){
p=access(p);
while(ch[p][1]) p=ch[p][1];
con(p,v,1);splay(v);
}
IL int qry(int p){
splay(p);
while(ch[p][1]) p=ch[p][1];
splay(p);
return p;
}
}
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
int n,op;
int cover[N],border[N],top[N];
int anc[N][20];
char s[N];
int ans[N];
long long res;
IL bool check(int i,int x){
if(!x) return 1;
return i-LCT::qry(x)<=x;
}
int main(){
freopen("cover.in","r",stdin);
freopen("cover.out","w",stdout);
n=read();op=read();
char cc=getchar();
while(cc<'a'||cc>'z') cc=getchar();
for(int i=1;i<=n;++i) s[i]=cc,cc=getchar();
for(int i=1,j=0;i<=n;++i){
if(op) s[i]=(s[i]+ans[i-1])%26+97;
if(i>1){
while(j&&s[j+1]!=s[i]) j=border[j];
if(s[j+1]==s[i]) ++j;
}
anc[i][0]=border[i]=j;
for(int t=1;t<20;++t) anc[i][t]=anc[anc[i][t-1]][t-1];
int p=border[i];
while(top[p]&&!check(i,top[p])) p=border[top[p]];
if(p){
if(check(i,p)) cover[i]=p;
else{
for(int t=19;~t;--t)
if(anc[p][t]>top[p]&&!check(i,anc[p][t])) p=anc[p][t];
cover[i]=border[p];
}
}
if(border[i]==cover[i]&&cover[i]) top[i]=top[cover[i]];
else top[i]=i;
ans[i]=cover[i]^ans[cover[i]];
if(cover[i]) LCT::add(cover[i],i);
res+=ans[i];
}
printf("%lld\n",res);
return 0;
}

T3

给定边带权的树上若干条路径,你需要从中选出 k 条使得路径交长度最大。

Sol 给的启发式合并的想法很牛!学习一下!

考虑一个暴力就是你枚举最终路径交的端点中较靠下的那一个 x,这样考虑所有恰好只有一个端点在 x 子树中的所有路径,将这些路径进行路径点权 +1 之后,求出距离 x 最远的点权 k 的点更新答案。

考虑 DSU on tree 维护子树信息,类似维护异或一样,往桶里第一次加一条路径点权 +,第二次加这条路径进行路径点权 - 操作。

考虑到只有在合并轻子树时,有修改点权的那些点才可能更新答案,否则其到 x 子树中早就更新了答案,而且距离还一定更远。

但是怎么维护所有修改后点权 k 的最远的点呢?注意到这个题有两种单调性:除了 x 的祖先以外,所有点的点权越往上越大,而 x 到根的链越往下越大,这两个部分分别线段树上二分即可。复杂度 O(nlog3n)

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
typedef long long ll;
ll ans;int su,sv;
const int N=200003,M=230003;
int n,m,k;
int hd[N],ver[N<<1],nxt[N<<1],val[N<<1],tot;
vector<int> vec[N];
void add(int u,int v,int w){
nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;val[tot]=w;
}
int dfn[N],od[N],num;
int sz[N],sn[N],ft[N],tp[N],de[N],clen[N];
ll dep[N];int anc[N][18];
int eu[M],ev[M],ew[M];
void dfs(int u,int fa){
sz[u]=1;anc[u][0]=ft[u]=fa;
for(int t=1;t<18;++t) anc[u][t]=anc[anc[u][t-1]][t-1];
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa) continue;
dep[v]=dep[u]+val[i];
de[v]=de[u]+1;
dfs(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[sn[u]]) sn[u]=v;
}
}
void split(int u,int top){
od[dfn[u]=++num]=u;tp[u]=top;
if(sn[u]) split(sn[u],top),clen[u]=clen[sn[u]]+1;
else clen[u]=1;
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==ft[u]||v==sn[u]) continue;
split(v,v);
}
}
inline int lca(int u,int v){
while(tp[u]^tp[v])
if(de[tp[u]]>de[tp[v]]) u=ft[tp[u]];
else v=ft[tp[v]];
return de[u]<de[v]?u:v;
}
void record(int u,int v,int w){
if(u>v) swap(u,v);
ll ws=dep[u]+dep[v]-2*dep[w];
if(ws>ans) ans=ws,su=u,sv=v;
if(ws==ans&&u<su) su=u,sv=v;
if(ws==ans&&u==su&&v<sv) sv=v;
}
struct ds{
vector<int> tr;
int bit,len;
void init(int _len){bit=__lg(len=_len);tr.resize(len+1);}
void upd(int x,int v){while(x<=len) tr[x]+=v,x+=(x&-x);}
int ask(){
int v=k,x=0;
for(int i=bit;~i;--i)
if(x+(1<<i)<=len&&tr[x+(1<<i)]<v) v-=tr[x+=(1<<i)];
return x+1;
}
int qry(int x){
int res=0;
while(x) res+=tr[x],x^=(x&-x);
return res;
}
}DS[N];
bool vis[N];
int exi[M],cur[M];
int seq[N],rk;
namespace DSU1{
void chain(int x,int v){
while(x){
int ps=dfn[x]-dfn[tp[x]];
x=tp[x];
if(!vis[x]) vis[x]=1,seq[++rk]=x;
DS[x].upd(clen[x]-ps,v);
x=ft[x];
}
}
void opt(int u,int x){
if(exi[x]) chain(exi[x],-1),exi[x]=0;
else chain(exi[x]=eu[x]^ev[x]^u,1);
}
void trav(int u){
for(int x:vec[u]) opt(u,x);
for(int i=hd[u];i;i=nxt[i]) if(ver[i]!=ft[u]) trav(ver[i]);
}
void sol(int u,bool del){
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==ft[u]||v==sn[u]) continue;
sol(v,1);
}
if(sn[u]) sol(sn[u],0);
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==ft[u]||v==sn[u]) continue;
trav(v);
}
for(int x:vec[u]) opt(u,x);
while(rk){
int x=seq[rk--];
vis[x]=0;
int t=DS[x].ask();
if(t<=DS[x].len){
int p=od[dfn[x]+clen[x]-t],q=lca(p,u);
if(q!=u&&q!=p&&dfn[q]<=dfn[p]) record(u,p,q);
}
}
if(del) trav(u);
}
}
namespace DSU2{
void chain(int x,int y,int v){
while(true){
int ps=dfn[x]-dfn[tp[x]];
x=tp[x];
DS[x].upd(clen[x]-ps,v);
if(x==tp[y]) return DS[x].upd(clen[x]-dfn[y]+dfn[x]+1,-v);
x=ft[x];
}
}
void opt(int u,int x){
if(exi[x]) chain(exi[x],ew[x],-1),exi[x]=0;
else chain(exi[x]=u,ew[x],1);
}
void trav(int u){
for(int x:vec[u]) opt(u,x);
for(int i=hd[u];i;i=nxt[i]) if(ver[i]!=ft[u]) trav(ver[i]);
}
inline int calc(int u){
int x=tp[u];
return DS[x].qry(clen[x]-dfn[u]+dfn[x]);
}
void sol(int u,bool del){
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==ft[u]||v==sn[u]) continue;
sol(v,1);
}
if(sn[u]) sol(sn[u],0);
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==ft[u]||v==sn[u]) continue;
trav(v);
}
for(int x:vec[u]) opt(u,x);
int v=u;
for(int t=17;~t;--t)
if(anc[v][t]&&calc(anc[v][t])>=k) v=anc[v][t];
if(u^v) record(u,v,v);
if(del) trav(u);
}
}
int main(){
freopen("stroll.in","r",stdin);
freopen("stroll.out","w",stdout);
n=read();m=read();k=read();
for(int i=1;i<n;++i){
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}
dfs(1,0);split(1,1);
for(int i=1;i<=m;++i){
vec[eu[i]=read()].emplace_back(i);
vec[ev[i]=read()].emplace_back(i);
ew[i]=lca(eu[i],ev[i]);
}
for(int i=1;i<=n;++i) if(tp[i]==i) DS[i].init(clen[i]);
DSU1::sol(1,1);DSU2::sol(1,1);
printf("%lld\n%d %d\n",ans,su,sv);
return 0;
}
posted @   yyyyxh  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2023-08-15 YsOI2023 小记
点击右上角即可分享
微信分享提示