0814 训练
梦熊苍穹第 31 场。Good Round。
做出了 T1/T2,T2 做法似乎有点憨……
T1
给定环上的若干个区间,两个点连边当且仅当对应的区间有交,求该图最大团。
考虑如果是链上的若干个区间,答案很明显是覆盖次数最大的那个点。
考虑断环成链,随便选一个点将环破开,拎出所有经过该点的区间。那么由上述结论,最大团中所有没有经过该点的区间应该有一个公共点。
枚举公共点,发现经过破开点的所有区间肯定不互相冲突,而经过公共点的所有区间也不互相冲突。所以这是二分图最大独立集问题。
二分图的连边方式是二维偏序流,直接上贪心流可以做到 \(O(n\log n)\) 求最大流。
综上复杂度为 \(O(n^2\log n)\)。
#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\) 的一次出现包含,那么称 \(t\) 是 \(s\) 的 \(\text{cover}\),求每个前缀的 \(\text{cover}\) 长度异或和,强制在线。
考虑到 \(\text{cover}\) 一定是 \(\text{border}\),我们猜测 \(\text{cover}\) 是否有跟 \(\text{border}\) 一样的性质。
首先明显 \(\text{cover}\) 的 \(\text{cover}\) 是 \(\text{cover}\)。其次如果一个字符串有两个不同的 \(\text{cover}\),首先我们可以立马得到它们有 \(\text{border}\) 关系,既然是 \(\text{border}\) 我们就可以立马得出截取大的 \(\text{cover}\) 覆盖原串方案的前缀,加上 \(\text{border}\) 的两次出现其一定可以被小的覆盖的。
那么这样我们就知道了 \(\text{cover}\) 的偏序关系构成一颗树,这棵树是 \(\text{border}\) 树的虚树。
现在考虑只需要建出 \(\text{cover}\) 树就做完了,即要找最大 \(\text{cover}\)。
我们考虑在 \(\text{border}\) 树上跳到第一个是 \(\text{cover}\) 的位置。考虑用 \(\text{border}\) 性质优化。一个观察是所有的大于一半的 \(\text{border}\) 都是 \(\text{cover}\),这代表着将 \(cover_i=border_i\) 的树边建出来,那么任何一个点到根都只需要经过 \(O(\log n)\) 条 \(cover_i\ne border_i\) 的边。
而 \(cover\) 性在 \(cover\) 树上有传递性,也就是你跳 \(border\) 树时,你可以在 \(cover_i=border_i\) 的直链上二分出最大的 \(\text{cover}\)。用 LCT 维护每个 \(\text{cover}\) 的最后一次出现再二分可以做到两只 \(\log\)。我场上写的这个做法,直接过了!
更好一点的做法是考虑继续利用 \(cover_i=border_i\) 的链的性质,更新最后一次出现时直接在上面跳更新链顶,然后 LCT 上二分解决最后一条链上的情况。复杂度来到了 \(O(n\log n)\)。
或者像 zhy 一样更好的想法,注意到大于一半的 \(\text{border}\) 构成等差数列,也就是说所有大 \(\text{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\) 最远的点权 \(\ge k\) 的点更新答案。
考虑 DSU on tree 维护子树信息,类似维护异或一样,往桶里第一次加一条路径点权 +,第二次加这条路径进行路径点权 - 操作。
考虑到只有在合并轻子树时,有修改点权的那些点才可能更新答案,否则其到 \(x\) 子树中早就更新了答案,而且距离还一定更远。
但是怎么维护所有修改后点权 \(\ge k\) 的最远的点呢?注意到这个题有两种单调性:除了 \(x\) 的祖先以外,所有点的点权越往上越大,而 \(x\) 到根的链越往下越大,这两个部分分别线段树上二分即可。复杂度 \(O(n\log^3 n)\)。
#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;
}

浙公网安备 33010602011771号