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;
}
posted @ 2024-08-15 08:28  yyyyxh  阅读(104)  评论(0)    收藏  举报