20210622模拟赛

早上迟到了,然后手机被没收了(痛苦面具

整个上午考试都好困,看教练不在就睡了半程2333

要结束的时候通过某奇怪的方式调出了写挂的bug

就是最后还是把代码拖到windows下来调了(我错了,我不敢了。

三道题分别是 dp,数据结构加速分治,子集(超集)卷。

T1:是个sb题 理解错题意然后弃了。

T2:目测 4k 代码,写了暴力溜了。

T3:切了。然而我时间多了个log极限卡常居然能过,教练机子nb

据说是三道原题,不知道(

今天题解写不完了,但是明天应该没有模拟赛(只要教练不抽风),所以咕到明天叭,先放代码。

updata 6.23 补完坑了!但是事实上今天还是有模拟赛,新的坑又增加了 55555。

A

倒过来做,不是很难的dp,略过。

B

区间问题,考虑分治。

对于左区间 [ L , M i d ] [L,Mid] [L,Mid] 我们维护它每个后缀的领域(圆),对于右区间 [ M i d + 1 , R ] [Mid+1,R] [Mid+1,R] 维护每个前缀的领域。领域我们用 C ( u , r ) C(u,r) C(u,r) 来表示, u u u 表示直径的中点, r r r 表示半径。 C ( u i , r i ) , i ∈ [ L , M i d ] C(u_i,r_i),i\in[L,Mid] C(ui,ri),i[L,Mid] 表示 [ i , M i d ] [i,Mid] [i,Mid] 中的点构成的领域。 C ( u j , r j ) , j ∈ [ M i d + 1 , R ] C(u_j,r_j),j\in[Mid+1,R] C(uj,rj),j[Mid+1,R] 表示 [ M i d + 1 , j ] [Mid+1,j] [Mid+1,j] 中的点构成的领域。

考虑算左区间的点 i i i 的贡献,即 l = i , r ∈ [ M i d + 1 , R ] l=i,r\in[Mid+1,R] l=i,r[Mid+1,R] 的直径的和。

那么右边序列一定存在一个前缀 [ M i d + 1 , a ] [Mid+1,a] [Mid+1,a] 满足前缀中的所有领域都包含了 C ( u i , r i ) C(u_i,r_i) C(ui,ri) ,此时直径为 r i × 2 r_i\times 2 ri×2 。当然前缀可以为空。

同时也存在一个后缀 [ b , R ] [b,R] [b,R] 满足 C ( u i , r i ) C(u_i,r_i) C(ui,ri) 包含后缀中的所有领域,此时直径的和为 ∑ j ∈ [ b , R ] r j × 2 \sum_{j\in[b,R]} r_j\times 2 j[b,R]rj×2

除这两个前后缀外,中间的领域要么和 C ( u i , r i ) C(u_i,r_i) C(ui,ri) 相离,要么相交。这样的两个领域合并起来的新的领域的直径为 两个中心点的距离+两个领域的半径。所以只需要求出点 u i u_i ui 到中间的领域的中心点的距离和即可。

这个用点分治可以解决,提前把点分树建出来,把右边需要求距离的中心点都挂上去,用完一个删一个,询问和修改都是 O ( log ⁡ n ) O(\log n) O(logn) 的 。

到现在我们只算了单个点 i i i 的贡献,但是显然 a a a b b b 随着左边领域的扩大也是单调不减的,所以挨着从 M i d Mid Mid 算到 L L L 就可以了。

分治 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,单次询问或修改点分树 O ( log ⁡ n ) O(\log n) O(logn) ,总时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

建议用树剖+树状数组代替点分,时间会多一个 log ⁡ \log log ,但是实测速度吊打点分(就很难受。

#include <bits/stdc++.h>
#define N 200005
using namespace std;
typedef long long ll;
void read(int &x){
	x=0; char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
int n,nn;
ll ans;
vector<int> ed[N];
int fa[N][20],lst[N][20],dep[N];
bool tag[N];
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--) 
		if(dep[lst[x][i]]>=dep[y]) x=lst[x][i];
	if(x==y) return x;
	for(int i=19;i>=0;i--) 
		if(lst[x][i]!=lst[y][i]) x=lst[x][i],y=lst[y][i];
	return lst[x][0];
}
int dis(int x,int y){
	int res=0;
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--) 
		if(dep[lst[x][i]]>=dep[y]) res+=(1<<i),x=lst[x][i];
	if(x==y) return res;
	for(int i=19;i>=0;i--) 
		if(lst[x][i]!=lst[y][i]) res+=(1<<i+1),x=lst[x][i],y=lst[y][i];
	return res+2;
}
int get_mid(int x,int y,int lcaa,int r){
//	cout<<x<<' '<<y<<' '<<r<<' '<<endl;
	int dist=dep[x]+dep[y]-(dep[lcaa]<<1);
	if(dep[x]-r<dep[lcaa]) swap(x,y),r=dist-r;
	int now=0;
	for(int i=19;i>=0;i--) if((now+(1<<i))<=r) now+=(1<<i),x=lst[x][i];
//	cout<<x<<endl;
	return x;
}
void init(int x){
//	cout<<lst[x][0]<<' '<<x<<endl;
	dep[x]=dep[lst[x][0]]+1;
	for(int i=1;i<=19;i++) lst[x][i]=lst[lst[x][i-1]][i-1];
	for(int y : ed[x]) if(y!=lst[x][0]) lst[y][0]=x,init(y);
}
int siz[N],maxn[N],far[N][19];
void get_root(int &rt,int sum,int x,int faa){
	siz[x]=1; maxn[x]=0;
	for(int y : ed[x]) 
		if(y!=faa&&!tag[y]) 
			get_root(rt,sum,y,x),siz[x]+=siz[y],maxn[x]=max(maxn[x],siz[y]);
	maxn[x]=max(maxn[x],sum-siz[x]);
	if(!rt||maxn[x]<maxn[rt]) rt=x;
}
void bfs(int rt,int x,int faa,int ty){
	siz[x]=1;
	fa[x][ty]=rt;
	for(int y : ed[x])
		if(y!=faa&&!tag[y])
			far[y][ty]=far[x][ty]+1,bfs(rt,y,x,ty),siz[x]+=siz[y];
}
void build(int x,int sum,int ty){
	int rt=0;
	get_root(rt,sum,x,0);
	bfs(rt,rt,0,ty);
	tag[rt]=1;
	for(int y : ed[rt]) if(!tag[y]) build(y,siz[y],ty+1);
}
struct edge{
	int mid,r;
}cc[N];
bool chk(int x,edge y){ return dis(y.mid,x)<=y.r; }
void merge(edge &x,edge y){ 
	int lcaa=lca(x.mid,y.mid),len=dep[x.mid]+dep[y.mid]-dep[lcaa]*2;
	if(len<=y.r){ x=y; return; } 
	x.r=(y.r+len)/2; x.mid=get_mid(x.mid,y.mid,lcaa,x.r); 
}
ll sum_dis[N],cnt[N];
ll NOW;
void add(edge x){
	NOW+=x.r; int u=x.mid;
	for(int i=1;fa[u][i];i++) sum_dis[fa[u][i]]+=far[u][i]-far[u][i-1],cnt[fa[u][i]]++;
} 
void del(edge x){
	NOW-=x.r; int u=x.mid;
	for(int i=1;fa[u][i];i++) sum_dis[fa[u][i]]-=far[u][i]-far[u][i-1],cnt[fa[u][i]]--;
} 
ll ask(int x){
	ll res=0;
	for(int i=1;fa[x][i];i++) res+=sum_dis[fa[x][i]]+cnt[fa[x][i]]*(far[x][i]-far[x][i-1]);
	return res; 
}
void solve(int l,int r,bool way){
	if(l==r){ cc[l]={l,0}; return; }
	int mid=(l+r)>>1;
	solve(l,mid,0),solve(mid+1,r,1);
	int a=mid,b=mid+1; ll sum=0;
	while(b<=r&&!chk(mid,cc[b])) add(cc[b]),b++;
	for(int i=b;i<=r;i++) sum+=cc[i].r<<1;
	for(int i=mid;i>=l;i--){
		while(b<=r&&!chk(i,cc[b])) sum-=(cc[b].r<<1),add(cc[b]),b++;
		while(a<r&&a+1<b&&chk(a+1,cc[i])) a++,del(cc[a]);
		ans+=1ll*(a-mid)*cc[i].r*2+sum+ask(cc[i].mid)+NOW+1ll*cc[i].r*(b-a-1);
	}
	for(int i=a+1;i<b;i++) del(cc[i]);
	if(way==0){
		cc[r]={r,0};
		for(int i=r-1;i>=l;i--) cc[i]={i,0},merge(cc[i],cc[i+1]);
	} 
	else{
		cc[l]={l,0};
		for(int i=l+1;i<=r;i++) cc[i]={i,0},merge(cc[i],cc[i-1]);
	}
}
int main(){
//	freopen("image26.in","r",stdin);
	read(n);
	int u,v;
	for(int i=1;i<n;i++){
		read(u),read(v);
		ed[u].push_back(i+n),ed[v].push_back(i+n);
		ed[i+n].push_back(u),ed[i+n].push_back(v);
	}
	nn=n+n-1;
	init(1);
	build(1,nn,1);
	solve(1,n,0);
	cout<<ans/2;
}

C

由于在第一个串直接交换一个区间比较棘手,考虑如何将一个区间的操作差分,这显然是不能在一个串上 实现的,因此我们可以考虑在第二个串上做文章(方便起见,下面使用 S S S, T T T 分别表示第一个串和第二个串)。

直接用前缀操作差分,观察可知对于 S S S 做一遍操作 l → r l → r lr 的答案和对 T T T 做一遍 r → l r → l rl 的答案是相同的,所 以进一步可以推出 对 S S S 操作 l → r l → r lr 的答案 等于 对 S S S 操作 l − 1 → 1 l − 1 → 1 l11 再对于 t t t 操作 r → 1 r → 1 r1

这样子的话我们可以 O ( n k ) O(nk) O(nk) 预处理出 f s f_s fs 表示 S S S 某个状态 s s s 出现的最早时间, g s g_s gs 表示 T T T 某个状态 s s s 出现 的最晚时间(注意是从该时刻反向操作的状态 s s s)。

事实上,最大化两个串相同的位数等价于最大化两个串均为 1 1 1 的位数。

S S S a a a 位为 1 1 1 T T T b b b 位为 1 1 1,公共为 1 1 1 的位数有 c c c 位,答案就是 c + ( k − c − ( a − c ) − ( b − c ) ) = k + 2 c − a − b c+(k−c−(a−c)−(b−c)) = k+2c−a−b c+(kc(ac)(bc))=k+2cab, 最大化 c c c 即可。 考虑直接枚举交集 s s s,然后再使用上面预处理的 f f f, g g g 判断是否存在 g t − f t ≥ M , s ⊆ t g_t − f_t ≥ M, s ⊆ t gtftM,st,如果直接枚举子集 复杂度 O ( k 3 k ) O(k3^k ) O(k3k),直接高维后缀 m i n / m a x min/max min/max 优化复杂度就是 O ( k 2 k ) O(k2^k) O(k2k) 了。 总复杂度 O ( n k + k 2 k ) O(nk + k2^k) O(nk+k2k)

下面的程序时间是多乘了一个 k k k 的,但是它过了。(不管

#include <bits/stdc++.h>
#define N 1100006
using namespace std;
int ti[N][11],a[N],b[N],o;
int n,m,k,cc[22],dd[22],SS,TT,maxn;
char S[22],T[22];
vector<int> pos_s,pos_t;
void read(int &x){
	x=0; char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
}
void init(int now,int t,int num){
	if(!num||ti[now][num]!=o) return;
	ti[now][num]=t;
	for(int i=0;i<k;i++) if(now&(1<<i)) init(now^(1<<i),t,num-1);
}
int ansl=1,ansr,ans=0;
int main(){
//	freopen("option3.in","r",stdin);
//	freopen("option.out","w",stdout);
	memset(ti,127/3,sizeof(ti)); o=ti[0][0];
	read(n),read(m),read(k); maxn=(1<<k)-1; ansr=m;
	scanf("%s",S),scanf("%s",T);
	int now=0;
	for(int i=0;i<k;i++){
		now|=((S[i]=='1')<<i);
		cc[i]=dd[i]=i;
		if(S[i]=='1') pos_s.push_back(i),SS++;
		if(T[i]=='1') pos_t.push_back(i),TT++;
	}
	if(SS>10){
		now=SS=TT=0;
		pos_s.clear(),pos_t.clear();
		for(int i=0;i<k;i++){
			now|=((S[i]=='0')<<i);
			cc[i]=dd[i]=i;
			if(S[i]=='0') pos_s.push_back(i),SS++;
			if(T[i]=='0') pos_t.push_back(i),TT++;
		}
	}
	init(now,0,SS);
	int u,v;
	for(int i=1;i<=n;i++){
		read(a[i]),read(b[i]); a[i]--,b[i]--;
		swap(cc[a[i]],cc[b[i]]);
		now=0;
		for(int j : pos_s) now|=(1<<cc[j]);
		init(now,i,SS);
	}
	int cnt=0,x;
	for(int i=0;i<=maxn;i++){
		cnt=0;
		for(int l=0;l<k;l++) cnt+=(i&(1<<l))?1:0;
		if(cnt>=TT) continue;  
		for(int j=1;j<=SS;j++){
			if(ti[i][j]==o) break;
			for(int l=0;l<k;l++){
				if(!((1<<l)&i))
				ti[(1<<l)|i][j]=min(ti[(1<<l)|i][j],ti[i][j]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		swap(dd[a[i]],dd[b[i]]);
		now=0;
		for(int j : pos_t) now|=(1<<dd[j]);
		for(int j=ans+1;j<=SS;j++){
			if(ti[now][j]<=i-m) ans=j,ansl=ti[now][j]+1,ansr=i;
		}
	}
	cout<<k-(SS-ans)-(TT-ans)<<'\n';
	cout<<ansl<<' '<<ansr<<'\n';
}
posted @ 2022-10-10 20:18  缙云山车神  阅读(8)  评论(0编辑  收藏  举报