0814 训练
梦熊苍穹第 31 场。Good Round。
做出了 T1/T2,T2 做法似乎有点憨……
T1
给定环上的若干个区间,两个点连边当且仅当对应的区间有交,求该图最大团。
考虑如果是链上的若干个区间,答案很明显是覆盖次数最大的那个点。
考虑断环成链,随便选一个点将环破开,拎出所有经过该点的区间。那么由上述结论,最大团中所有没有经过该点的区间应该有一个公共点。
枚举公共点,发现经过破开点的所有区间肯定不互相冲突,而经过公共点的所有区间也不互相冲突。所以这是二分图最大独立集问题。
二分图的连边方式是二维偏序流,直接上贪心流可以做到 求最大流。
综上复杂度为 。
#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
定义如果一个字符串 能完整覆盖 ,即 中的每一个下标都至少被 的一次出现包含,那么称 是 的 ,求每个前缀的 长度异或和,强制在线。
考虑到 一定是 ,我们猜测 是否有跟 一样的性质。
首先明显 的 是 。其次如果一个字符串有两个不同的 ,首先我们可以立马得到它们有 关系,既然是 我们就可以立马得出截取大的 覆盖原串方案的前缀,加上 的两次出现其一定可以被小的覆盖的。
那么这样我们就知道了 的偏序关系构成一颗树,这棵树是 树的虚树。
现在考虑只需要建出 树就做完了,即要找最大 。
我们考虑在 树上跳到第一个是 的位置。考虑用 性质优化。一个观察是所有的大于一半的 都是 ,这代表着将 的树边建出来,那么任何一个点到根都只需要经过 条 的边。
而 性在 树上有传递性,也就是你跳 树时,你可以在 的直链上二分出最大的 。用 LCT 维护每个 的最后一次出现再二分可以做到两只 。我场上写的这个做法,直接过了!
更好一点的做法是考虑继续利用 的链的性质,更新最后一次出现时直接在上面跳更新链顶,然后 LCT 上二分解决最后一条链上的情况。复杂度来到了 。
或者像 zhy 一样更好的想法,注意到大于一半的 构成等差数列,也就是说所有大 的树边组成的是若干条链!这是一个天然的树剖结构,免去了 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
给定边带权的树上若干条路径,你需要从中选出 条使得路径交长度最大。
Sol 给的启发式合并的想法很牛!学习一下!
考虑一个暴力就是你枚举最终路径交的端点中较靠下的那一个 ,这样考虑所有恰好只有一个端点在 子树中的所有路径,将这些路径进行路径点权 +1 之后,求出距离 最远的点权 的点更新答案。
考虑 DSU on tree 维护子树信息,类似维护异或一样,往桶里第一次加一条路径点权 +,第二次加这条路径进行路径点权 - 操作。
考虑到只有在合并轻子树时,有修改点权的那些点才可能更新答案,否则其到 子树中早就更新了答案,而且距离还一定更远。
但是怎么维护所有修改后点权 的最远的点呢?注意到这个题有两种单调性:除了 的祖先以外,所有点的点权越往上越大,而 到根的链越往下越大,这两个部分分别线段树上二分即可。复杂度 。
#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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2023-08-15 YsOI2023 小记