6月CWOI杂题

C0253 【0617 C组】模拟测试

军训归来的第一场模拟赛,小寄。

C 【0601 C组】树

好神奇的题目。

直径这个东西没什么能入手的性质,我们先考虑进行一些转化。

对于直径,我们去找它的中心点。中心点可能在边上,于是把边拆开,比如边 \((u,v)\) 拆成 \((u,x)(x,v)\),这样就有了 \(2n-1\) 个点,且直径的中心点都在点上。于是可以进一步把问题变成每次使一条边 \(+2\),使直径的一半(即半径,就是以中心点为端点延伸的最长路径)最小。至于为什么有半径就一定能找到对应直径的问题,我们下面会讲。

枚举中心点 \(i\),设离它最远的点距离为 \(r_i\),显然这样的点一定是叶子。定义所有叶子到 \(i\) 的距离之和为 \(s_i\),整棵树的叶子数为 \(c\),这些东西和 \(r_i\) 都可以换根 \(O(n)\)

那么我们要把以 \(i\) 为中心点的半径控制在 \(r_i\) 最多可以操作 \(\dfrac{c\cdot r_i-s_i}{2}\) 次,如果半径为 \(r_i+2x\) 最多可以操作 \(\dfrac{c\cdot r_i-s_i}{2}+c\cdot x\) 次。因为叶子不可能作为中心点,所以 \(c\) 是定值。记 \(\dfrac{c\cdot r_i-s_i}{2}\)\(o_i\),小推一下可以得到 \(x=\left\lceil\dfrac{k-o_i}{c}\right\rceil\),那么 \(i\) 点的答案就是:\(ans_i=\begin{cases}r_i&k\le o_i\\r_i+2\left\lceil\dfrac{k-o_i}{c}\right\rceil&k>o_i\end{cases}\)

解释一下为啥有半径就一定能找到对应直径。因为我们是直接粗暴地认为直径长为半径两倍,如果你的另一半不足 \(r_i\),首先根据定义它不是中心点;其次,因为中心点一定在点上,所以在枚举其他点的时候我们会得到正确的答案,故 \(2r_i\) 这个东西是不优的,不会对答案造成影响。

再解释一下为什么 \(o_i\) 是整数。叶子节点都是原来就有的,所以它们到 \(i\) 的距离的奇偶性相同。

现在问题转化成每次给你 \(k\),求在上述柿子中能得到的最小值。把柿子和询问离线下来,分别按照 \(o_i\)\(k\) 从小到大排序。枚举 \(k\),那么就是有一段前缀柿子取到第二类而另一段后缀取第一类。分界点可以双指针。后缀那坨可以直接取最小值,前面的可以把上取整转化成根据余数分一下类,然后丢到线段树上。时间复杂度 \(O(n\log n)\)

有一个细节:从 \(u\) 换根到 \(v\) 的时候可能会出现 \(u\) 就是叶子。

upd:好像可以不用线段树了捏。

可以把 \(r_i\) 写进上取整里面:如果 \(r_i\) 是偶数,柿子可以写成 \(2\left\lceil\dfrac{r_i}{2}+\dfrac{k-o_i}{c}\right\rceil=2\left\lceil\left(\dfrac{r_i}{2}-\dfrac{o_i}{c}\right)+\dfrac{k}{c}\right\rceil\),维护最大的 \(\left(\dfrac{r_i}{2}-\dfrac{o_i}{c}\right)\) 即可;如果 \(r_i\) 是奇数,先减一写进去最后再加一就是一样的了。精度要取高一点。

点击查看代码(线段树)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[800005];
int head[400005],tot,deg[400005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int d[400005],cnt[400005],tag[400005],in[400005];
void dfs1(int u,int fa){
	cnt[u]=tag[u],in[u]=(tag[u]?0:-inf);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		d[v]=d[u]+1;dfs1(v,u);
		cnt[u]+=cnt[v],in[u]=max(in[u],in[v]+1);
	}
}
int out[400005],s[400005],r[400005],o[400005];
void dfs2(int u,int fa){
	vector<pii>vec;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		s[v]=s[u]-cnt[v]+(cnt[1]-cnt[v]);
		vec.push_back(mk(in[v],v));
		out[v]=out[u]+1;
		if(tag[u])out[v]=max(out[v],1ll);
	}
	int pre=-inf;
	for(int i=0;i<(int)vec.size();i++){
		out[vec[i].se]=max(out[vec[i].se],pre+2);
		pre=max(pre,vec[i].fi);
	}
	int suf=-inf;
	for(int i=(int)vec.size()-1;i>=0;i--){
		out[vec[i].se]=max(out[vec[i].se],suf+2);
		suf=max(suf,vec[i].fi);
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs2(v,u);
	}
}
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mi;
	}c[1600005];
	void pushup(int p){ 
		c[p].mi=min(c[ls].mi,c[rs].mi);  
	}
	void build(int l,int r,int p){
		if(l==r){c[p].mi=inf;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){c[p].mi=min(c[p].mi,k);return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L>R)return inf;
		if(L<=l&&r<=R)return c[p].mi;
		int mid=(l+r)>>1,res=inf;
		if(L<=mid)res=min(res,query(lson,L,R));
		if(R>mid)res=min(res,query(rson,L,R));
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
struct Dec{
	int o,id;
}p[400005];
int cmpD(Dec x,Dec y){
	return x.o<y.o;
}
int suf[400005];
struct Que{
	int k,id;
}q[200005];
int cmpQ(Que x,Que y){
	return x.k<y.k;
}
int A[400005],B[400005],ans[200005];
void solve(){
	int n=read();
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		add(u,n+i),add(n+i,u);
		add(v,n+i),add(n+i,v);
	}
	int m=2*n-1,Q=read(),c=0;
	for(int i=1;i<=m;i++){
		if(deg[i]==1)c++,tag[i]=1;
	} 
	dfs1(1,0);
	for(int i=1;i<=m;i++){
		if(tag[i])s[1]+=d[i];
	}
	out[1]=-inf;
	dfs2(1,0);
	int num=0;
	for(int i=1;i<=m;i++){
		r[i]=max(in[i],out[i]);
		o[i]=(r[i]*c-s[i])/2;
		A[i]=o[i]/c,B[i]=o[i]%c;
		if(!tag[i])p[++num]=(Dec){o[i],i};
	}
	sort(p+1,p+num+1,cmpD);
	suf[num+1]=inf;
	for(int i=num;i>=1;i--){
		suf[i]=min(suf[i+1],r[p[i].id]);
	}
	for(int i=1;i<=Q;i++){
		q[i].k=read(),q[i].id=i;
	} 
	sort(q+1,q+Q+1,cmpQ);
	Tr.build(0,c-1,1);
	for(int i=1,j=1;i<=Q;i++){
		while(j<=num&&p[j].o<=q[i].k){
			Tr.update(0,c-1,1,B[p[j].id],-2*A[p[j].id]+r[p[j].id]);
			j++;
		}
		int res=inf;
		res=min(res,suf[j]);
		res=min(res,(q[i].k/c)*2+Tr.query(0,c-1,1,0,q[i].k%c-1)+2);
		res=min(res,(q[i].k/c)*2+Tr.query(0,c-1,1,q[i].k%c,c-1));
		ans[q[i].id]=res;
	}
	for(int i=1;i<=Q;i++){
		printf("%lld\n",ans[i]);
	}
}
signed main(){
	int T=1;
	while(T--)solve();
	return 0;
}
点击查看代码(不用线段树)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
#define ldb long double
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
const ldb eps=1e-8;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int Ceil(ldb x){
	int y=(int)x;x-=y;
	if(fabsl(x)>eps)y++;
	return y;
}
struct edge{
	int v,nxt;
}e[800005];
int head[400005],tot,deg[400005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int d[400005],cnt[400005],tag[400005],in[400005];
void dfs1(int u,int fa){
	cnt[u]=tag[u],in[u]=(tag[u]?0:-inf);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		d[v]=d[u]+1;dfs1(v,u);
		cnt[u]+=cnt[v],in[u]=max(in[u],in[v]+1);
	}
}
int out[400005],s[400005],r[400005],o[400005];
void dfs2(int u,int fa){
	vector<pii>vec;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		s[v]=s[u]-cnt[v]+(cnt[1]-cnt[v]);
		vec.push_back(mk(in[v],v));
		out[v]=out[u]+1;
		if(tag[u])out[v]=max(out[v],1ll);
	}
	int pre=-inf;
	for(int i=0;i<(int)vec.size();i++){
		out[vec[i].se]=max(out[vec[i].se],pre+2);
		pre=max(pre,vec[i].fi);
	}
	int suf=-inf;
	for(int i=(int)vec.size()-1;i>=0;i--){
		out[vec[i].se]=max(out[vec[i].se],suf+2);
		suf=max(suf,vec[i].fi);
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs2(v,u);
	}
}
int p[400005],suf[400005],k[200005],q[200005],ans[200005];
int cmpD(int x,int y){
	return o[x]<o[y];
}
int cmpQ(int x,int y){
	return k[x]<k[y];
}
void solve(){
	int n=read(),m=2*n-1;
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,n+i),add(n+i,u),add(v,n+i),add(n+i,v);
	int Q=read(),c=0;
	for(int i=1;i<=m;i++)if(deg[i]==1)c++,tag[i]=1;
	dfs1(1,0);
	for(int i=1;i<=m;i++)if(tag[i])s[1]+=d[i];
	out[1]=-inf;dfs2(1,0);
	int num=0;
	for(int i=1;i<=m;i++){
		r[i]=max(in[i],out[i]),o[i]=(r[i]*c-s[i])/2;
		if(!tag[i])p[++num]=i;
	}
	sort(p+1,p+num+1,cmpD);
	suf[num+1]=inf;for(int i=num;i>=1;i--)suf[i]=min(suf[i+1],r[p[i]]);
	for(int i=1;i<=Q;i++)k[i]=read(),q[i]=i;
	sort(q+1,q+Q+1,cmpQ);
	ldb odd=inf,even=inf;
	for(int i=1,j=1;i<=Q;i++){
		while(j<=num&&o[p[j]]<=k[q[i]]){
			if(r[p[j]]%2==0)even=min(even,(ldb)(-o[p[j]]*1.0L/c+r[p[j]]/2));
			else odd=min(odd,(ldb)(-o[p[j]]*1.0L/c+r[p[j]]/2));
			j++;
		}
		int res=inf;
		res=min(res,suf[j]);
		res=min(res,1ll+2ll*Ceil(odd+k[q[i]]*1.0L/c));
		res=min(res,2ll*Ceil(even+k[q[i]]*1.0L/c));
		ans[q[i]]=res;
	}
	for(int i=1;i<=Q;i++)printf("%lld\n",ans[i]);
}
signed main(){
	int T=1;
	while(T--)solve();
	return 0;
}

C0266 【0624 C组】模拟测试

感觉 \(\ldots\) 不如 C 组,难度。

T3 数组开小,小丑一个。

A 【1102 B组】跳跃

根据余数大小分个类转移,萌萌题。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int ma;
	}c[500005];
	void pushup(int p){
		c[p].ma=max(c[ls].ma,c[rs].ma);
	}
	void build(int l,int r,int p){
		if(l==r){
			c[p].ma=-inf;
			return;
		}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){
			c[p].ma=max(c[p].ma,k);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	} 
	int query(int l,int r,int p,int L,int R){
		if(L>R)return -inf;
		if(L<=l&&r<=R)return c[p].ma;
		int mid=(l+r)>>1;
		if(R<=mid)return query(lson,L,R);
		if(L>mid)return query(rson,L,R);
		return max(query(lson,L,R),query(rson,L,R));
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int t[100005],b[100005],f[100005],A[100005],B[100005],v[100005];
signed main(){
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	int k=read(),m=read(),d=read(),a=read(),n=read(),tot=0;
	t[0]=k,b[0]=0,v[++tot]=t[0]%d;
	t[n+1]=m,b[n+1]=0,v[++tot]=t[n+1]%d;
	for(int i=1;i<=n;i++)t[i]=read(),b[i]=read(),v[++tot]=t[i]%d;
	sort(v+1,v+tot+1);tot=unique(v+1,v+tot+1)-v-1;Tr.build(1,tot,1);
	for(int i=0;i<=n+1;i++)A[i]=t[i]/d,B[i]=lower_bound(v+1,v+tot+1,t[i]%d)-v;
	f[0]=0,Tr.update(1,tot,1,B[0],A[0]*a+f[0]);
	for(int i=1;i<=n+1;i++){
		f[i]=max(Tr.query(1,tot,1,1,B[i]-1)-a,Tr.query(1,tot,1,B[i],tot))-A[i]*a+b[i];
		Tr.update(1,tot,1,B[i],A[i]*a+f[i]);
	}
	printf("%lld\n",f[n+1]);
	return 0;
}

B 【1102 B组】匹配

\(P,Q\) 匹配当且仅当对于任意 \(i<j\),满足 \(([P_i=P_j]\oplus[Q_i=Q_j])=0\)。这个玩意不好表示,我们转化一下:定义 \(L_{s,i}\)\(R_{s,i}\) 表示在 \(s\) 中满足 \(j<i,s_j=s_i\) 的最大的 \(j\)\(j>i,s_j=s_i\) 的最小的 \(j\),如果没有就为 0。那么 \(P,Q\) 匹配就等价于 \(\forall i\in[1,m],L_{P,i}=L_{Q,i}\)

现在,我们需要对 \(s\) 的每个长为 \(m\) 的子串求其是否与 \(t\) 匹配。这个匹配的条件可以写成哈希,所以你动态维护一下哈希值即可。注意判一下 \(L_{s,j}\) 不存在或超过当前枚举的边界的情况。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,SIZ=1e6,P=1145141,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int pw[1000005],a[1000005],b[1000005],ans[1000005],p[1000005];
int La[1000005],Ra[1000005],Lb[1000005],Rb[1000005];
signed main(){
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	pw[0]=1;
	for(int i=1;i<=SIZ;i++)pw[i]=(pw[i-1]*P)%mod;
	int T=read(),c=read();
	while(T--){
		int n=read(),m=read(),tot=0;
		for(int i=1;i<=n;i++)a[i]=read();
		for(int i=1;i<=m;i++)b[i]=read();
		if(n<m){puts("0\n");continue;}
		for(int i=1;i<=n;i++){
			La[i]=p[a[i]],p[a[i]]=i;
		}
		for(int i=1;i<=c;i++)p[i]=0;
		for(int i=n;i>=1;i--){
			Ra[i]=p[a[i]],p[a[i]]=i;
		}
		for(int i=1;i<=c;i++)p[i]=0;
		for(int i=1;i<=m;i++){
			Lb[i]=p[b[i]],p[b[i]]=i;
		}
		for(int i=1;i<=c;i++)p[i]=0;
		for(int i=m;i>=1;i--){
			Rb[i]=p[b[i]],p[b[i]]=i;
		}
		for(int i=1;i<=c;i++)p[i]=0;
		int ha=0,hb=0;
		for(int i=1;i<=m;i++){
			if(La[i]==0||La[i]<1)ha=(ha*P)%mod;
			else ha=(ha*P%mod+(i-La[i]+1))%mod;
		}
		for(int i=1;i<=m;i++){
			if(Lb[i]==0||Lb[i]<1)hb=(hb*P)%mod;
			else hb=(hb*P%mod+(i-Lb[i]+1))%mod;
		}
		if(ha==hb)ans[++tot]=1;
		for(int i=2;i+m-1<=n;i++){
			if(La[i-1]!=0&&La[i-1]>=i-1)ha=(ha-pw[m-1]*((i-1)-La[i-1]+1)%mod+mod)%mod;
			if(Ra[i-1]!=0&&Ra[i-1]<=(i-1)+m-1){
				ha=(ha-pw[((i-1)+m-1)-Ra[i-1]]*(Ra[i-1]-(i-1)+1)%mod+mod)%mod;
			}
			if(La[i+m-1]==0||La[i+m-1]<i)ha=(ha*P)%mod;
			else ha=(ha*P%mod+((i+m-1)-La[i+m-1]+1))%mod;
			if(ha==hb)ans[++tot]=i;
		}
		printf("%lld\n",tot);
		for(int i=1;i<=tot;i++)printf("%lld ",ans[i]);
		puts("");
	}
	return 0;
}

C 【1102 B组】图

\(k\) 就是 \(\text{dist}(s,t)\)。关于构造方案,把 \(s\rightarrow t\) 路径上的边分成一层一层的赋权即可,其他边随便。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,w,nxt;
}e[160005];
int tot,head[405];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,m,s,t,d[405],vis[405],ans[160005];
void dijkstra(){
	for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
	priority_queue<pii,vector<pii>,greater<pii> >q;
	d[s]=0,q.push(mk(d[s],s));
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(d[v]>=d[u]+1)d[v]=d[u]+1,ans[w]=d[v],q.push(mk(d[v],v));
		}
	}
}
signed main(){
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	n=read(),m=read(),s=read(),t=read();
	for(int i=1,u,v;i<=m;i++)u=read(),v=read(),add(u,v,i),add(v,u,i);
	dijkstra();printf("%lld\n",d[t]);
	for(int i=1;i<=m;i++)printf("%lld\n",min(d[t],ans[i]));
	return 0;
}

D 【1102 B组】异或

先把所有数丢到 trie 上去 dp。定义 \(f_u\) 表示在以 \(u\) 为根的子树里选若干个数,使它们满足题目中给定的条件的方案数(可以不选)。记 \(cnt_u\) 表示 \(u\) 子树内有多少个数,\(val_u\) 表示 \(u\) 点对应的真实值,\(bit(x,d)\) 表示 \(x\) 二进制下从高到低第 \(d\) 位的值。

  • 如果当前深度 \(d\) 满足 \(2^d>x\),则你在 \(ls(u)\)\(rs(u)\) 中选的数互相之间一定满足条件,只用保证内部合法。同时,显然左右是独立的。有 \(f_{u}=f_{ls(u)}\times f_{rs(u)}\)

  • 反之,那么你在左右两边至多能各选一个点。定义 \(g_{u,v}\) 表示在 \(u,v\) 中各选一个点的方案数。有 \(f_u=1+cnt_u+g_{ls(u),rs(u)}\)

同时,对于 \(g\) 的计算,我们也可以分情况讨论。

  • 如果当前深度 \(d\) 满足 \(bit(x,d)=1\),那么要满足异或不小于 \(x\) 就必须在 \(d\) 这一位走法不同。有 \(g_{u,v}=g_{ls(u),rs(v)}+g_{rs(u),ls(v)}\)

  • 反之,如果这一位异或出 1 了,我们就已经满足不小于 \(x\) 的目标了;如果还是 0,我们继续走。有 \(g_{u,v}=cnt_{ls(u)}\times cnt_{rs(v)}+cnt_{rs(u)}\times cnt_{ls(v)}+g_{ls(u),ls(v)}+g_{rs(u),rs(v)}\)

边界条件可以看代码。记得最后减去不选的方案。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353;
inline ll read(){
	ll x=0;int f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
ll qpow(ll b,int p){
	ll res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1)res=res*b%mod;
	return res;
}
int tot=1,cnt[18000005],son[18000005][2];ll val[18000005];
inline void insert(ll x){
	int u=1;
	for(int i=60;i>=0;i--){
		int o=(x>>i)&1ll;
		if(!son[u][o])son[u][o]=++tot;
		cnt[u]++,u=son[u][o];
	}
	val[u]=x,cnt[u]++;
}
inline ll dfs(int u,int v,int d,ll x){
	if(u==0||v==0)return 0ll;
	if(d==-1)return ((val[u]^val[v])>=x)?1ll*cnt[u]*cnt[v]%mod:0ll;
	if((x>>d)&1ll)return (dfs(son[u][0],son[v][1],d-1,x)+dfs(son[u][1],son[v][0],d-1,x))%mod; 
	else return (dfs(son[u][0],son[v][0],d-1,x)+dfs(son[u][1],son[v][1],d-1,x)+1ll*cnt[son[u][0]]*cnt[son[v][1]]%mod+1ll*cnt[son[u][1]]*cnt[son[v][0]]%mod)%mod;
}
inline ll solve(int u,int d,ll x){
	if(u==0)return 1ll;
	if(d==-1)return (x==0)?qpow(2ll,cnt[u]):(cnt[u]+1)%mod;
	if((x>>d)&1ll)return (cnt[u]+1+dfs(son[u][0],son[u][1],d-1,x))%mod;
	else return solve(son[u][0],d-1,x)*solve(son[u][1],d-1,x)%mod;
}
signed main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	int n=read();ll x=read();
	for(int i=1;i<=n;i++)insert(read());
	printf("%lld\n",(solve(1,60,x)-1+mod)%mod);
	return 0;
}

C0272 【0626 B组】模拟测试

感觉自己有点蠢,大方向出来了,结论想到了,但还是不会。

A 【1109 B组】串

打表找规律。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
	int n,k;scanf("%lld%lld",&n,&k);
	if(k==0){puts("0");for(int i=1;i<=n;i++)printf("0 ");return 0;}
	printf("%lld\n",(1+n/2)*(n/2)+(n&1)*(n/2+1));
	for(int i=1;i<=n;i++)printf((i<=k-1||i==(n-(n-k)/2))?"1 ":"0 ");
	return 0;
}

B 【1109 B组】艺术家

“保证任何两个区间都是不相交或包含的关系”,这句话提醒了我们把区间往树形结构上靠。把每个区间看成一个点,区间之间的包含关系使它们形成了一片森林。为了方便,我们再人为加上一个 \([0,n+1]\) 的区间作为根节点。

现在,一个区间合法的必要条件是它的儿子合法。所以我们只用考虑当前没合法过的区间中最下面的。更具体地,我们开一颗线段树,维护没合法过的区间中,最深的包含这个点的区间。显然一个区间最多合法一次,每次最多判断失败一次,总的是 \((n+m)\log n\) 的。

对于判断一个区间是否合法,我们定义 \(p_i\) 表示在 \(i\) 之前最大的满足 \(c_i=c_j\)\(j\)(没有就等于负无穷),那么一个区间 \([l,r]\) 合法就等价于 \(\max\limits_{i=l}^r\{p_i\}<l\),线段树和 set 维护 \(p_i\) 即可。

点击查看代码
#include<iostream>
#include<set>
#include<queue>
#include<algorithm>
#include<stack>
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,q,a[500005];
struct Line{
	int l,r,id;
}l[500005];
int cmpl(Line x,Line y){
	if(x.l^y.l)return x.l<y.l;
	return x.r>y.r;
}
int cmpi(Line x,Line y){
	return x.id<y.id;
}
struct edge{
	int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
set<int>s[500005];
struct segtree1{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int ma;
	}c[2000005];
	void pushup(int p){
		c[p].ma=max(c[ls].ma,c[rs].ma);  
	}
	void build(int l,int r,int p){
		if(l==r){
			auto it=s[a[l]].lower_bound(l);
			if(it==s[a[l]].begin())c[p].ma=-inf;
			else it--,c[p].ma=(*it);
			return;
		}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){
			c[p].ma=k;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].ma;
		int mid=(l+r)>>1,res=-inf;
		if(L<=mid)res=max(res,query(lson,L,R));
		if(R>mid)res=max(res,query(rson,L,R));
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr1;
struct segtree2{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int s,tag;
	}c[2000005];
	void pushup(int p){
		c[p].s=c[ls].s+c[rs].s;
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
		c[ls].tag=c[p].tag,c[rs].tag=c[p].tag;
		c[ls].s=ln*c[p].tag,c[rs].s=rn*c[p].tag;
		c[p].tag=0;  
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){c[p].s=0;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int L,int R,int k){
		if(L<=l&&r<=R){c[p].s=(r-l+1)*k,c[p].tag=k;return;}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)update(lson,L,R,k);
		if(R>mid)update(rson,L,R,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].s;
		int mid=(l+r)>>1,res=0;pushdown(l,r,p);
		if(L<=mid)res+=query(lson,L,R);
		if(R>mid)res+=query(rson,L,R);
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr2;
int Fa[500005],tim[500005],son[500005];
void dfs(int u,int fa){
	if(u!=m+1)Tr2.update(1,n,1,l[u].l,l[u].r,u);
	Fa[u]=fa;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		son[u]++;dfs(v,u);
	}
}
signed main(){
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;i++)a[i]=read(),s[a[i]].insert(i);
	Tr1.build(1,n,1);
	for(int i=1;i<=m;i++)l[i].l=read(),l[i].r=read(),l[i].id=i,tim[i]=inf;
	sort(l+1,l+m+1,cmpl);stack<int>S;S.push(m+1);l[m+1]=(Line){0,n+1,m+1};
	for(int i=1;i<=m;i++){
		while(!S.empty()&&(l[S.top()].l>l[i].l||l[i].r>l[S.top()].r))S.pop();
		if(!S.empty())add(l[S.top()].id,l[i].id),add(l[i].id,l[S.top()].id);
		S.push(i);
	}
	sort(l+1,l+m+1,cmpi);Tr2.build(1,n,1);dfs(m+1,0);
	queue<int>Q;
	for(int i=1;i<=m;i++){
		if(son[i]==0)Q.push(i);
	} 
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		if(Tr1.query(1,n,1,l[u].l,l[u].r)<l[u].l){
			tim[u]=0;
			Tr2.update(1,n,1,l[u].l,l[u].r,Fa[u]);
			if(Fa[u]!=m+1&&Fa[u]!=0)Q.push(Fa[u]);
		}
	}
	for(int i=1,x,y;i<=q;i++){
		x=read(),y=read();
		auto it=s[a[x]].lower_bound(x);it++;
		if(it!=s[a[x]].end()){
			int pre,suf=*it;it--;
			if(it!=s[a[x]].begin())it--,pre=*it;
			else pre=-inf;
			Tr1.update(1,n,1,suf,pre);
		}
		s[a[x]].erase(x);
		s[y].insert(x),a[x]=y;
		it=s[y].lower_bound(x);
		if(it!=s[y].begin()){
			it--;int pre=*it;it++;
			Tr1.update(1,n,1,x,pre);
		}
		else Tr1.update(1,n,1,x,-inf);
		it++;
		if(it!=s[y].end()){
			Tr1.update(1,n,1,*it,x);
		}
		while(1){
			int now=Tr2.query(1,n,1,x,x);
			if(now==m+1||now==0)break;
			if(Tr1.query(1,n,1,l[now].l,l[now].r)<l[now].l){
				tim[now]=i;
				Tr2.update(1,n,1,l[now].l,l[now].r,Fa[now]);
			}
			else break;			
		}
	}
	int ans=0;
	for(int i=1;i<=m;i++){
		if(tim[i]==inf)ans^=i+m;
		else ans^=tim[i];
	}
	printf("%d\n",ans);
	return 0;
}

C 【1109 B组】黑白树

手玩可以得到一个性质:如果直径两端点不同色,则答案一定是一个点到直径其中一个端点的距离(当然,如果同色,答案就是直径)。然后我们发现设答案为 \(d\),直径两端点为 \(u,v\),那么如果点 \(i\) 满足 \(\text{dist}(i,u/v)>d\),那么 \(i\) 一定不能和 \(u/v\) 同色。发现这样只能满足答案 \(\le d\),所以我们可以从小到大枚举 \(d\),求出前缀和再作差即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{int v,nxt;}e[2000005];
int tot,head[1000005];
void add(int u,int v){e[++tot]=(edge){v,head[u]},head[u]=tot;}
int d[1000005],pw[1000005],s[1000005],cU[1000005],cV[1000005],num[5];
vector<int>vU[1000005],vV[1000005];
void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v!=fa)d[v]=d[u]+1,dfs(v,u);
	}
}
signed main(){
	int n=read();pw[0]=1;
	for(int i=1;i<=n;i++)pw[i]=2ll*pw[i-1]%mod;
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	d[1]=0;dfs(1,0);int U=1;for(int i=1;i<=n;i++)if(d[i]>d[U])U=i;
	d[U]=0;dfs(U,0);int V=1;for(int i=1;i<=n;i++)if(d[i]>d[V])V=i;
	for(int i=1;i<=n;i++)cU[i]=1,vU[d[i]].push_back(i);
	int D=d[V],ans=0;ans=(ans+2ll*D%mod*pw[n-2]%mod)%mod;
	d[V]=0;dfs(V,0);for(int i=1;i<=n;i++)cV[i]=1,vV[d[i]].push_back(i);
	cU[V]=0,cV[U]=0;for(int i=1;i<=n;i++)num[cU[i]+cV[i]]++;
	for(int d=D;d>=0;d--){
		if(num[0])s[d]=0;else s[d]=pw[num[2]];
		for(auto x:vU[d])num[cU[x]+cV[x]]--,cU[x]=0,num[cU[x]+cV[x]]++;
		for(auto x:vV[d])num[cU[x]+cV[x]]--,cV[x]=0,num[cU[x]+cV[x]]++;
	}
	for(int d=0,c;d<=D;d++){
		if(d==0)c=s[d];else c=(s[d]-s[d-1]+mod)%mod;
		ans=(ans+2ll*d%mod*c%mod)%mod;
	}
	printf("%d\n",ans);
	return 0;
}

D 【1109 B组】敢览求

不太会证复杂度。

C0276 【0628 B组】模拟测试

A 【0506 B组】博弈

显然,先手会优先砍断最小的环(因为这是在为他人做嫁衣),所以我们把数组从小到大排序,然后按顺序一个一个看。

假设当前枚举到 \(i\),不难发现转移至多有两种:1.先手砍断 \(a_i\),后手拿走 \(i\) 所有的绳结,同时需要砍断 \(a_{i+1}\)。这种情况可以看做双方交换了先后手顺序;2.当 \(a_i\ge 4\),先手砍断 \(a_i\),后手拿走 \(i\)\(a_i-4\) 个绳结,同时把 4 个绳结砍成 2+2 的形式,然后后手一定会拿走这 4 个绳结并需要砍断 \(a_{i+1}\)

定义 \(f_{i,0/1}\) 表示对于第 \(i\) 到第 \(n\) 个环,先手能取到的最小数量、以及此时后手的数量。为什么是最小值?因为先手实际上是没有选择权的,他的最优决策是随着后手的选择而固定的,所以后手可以“操纵”先手的行动。

注意:这里转移的时候 \(i\) 只能从 \(i+1\) 转移过来,不能迈更大的步子,即使这个转移明显不优也不行。为什么?因为你只能预测做了这个选择后,两人下一步的行动。对于更后面的决策,你不清楚也不需要。如果一次性从 \(i+2\) 或者更后面转移的话相当于你“钦定”了两人的行动,这显然是错的。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[105];pii f[105];
signed main(){
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
	int n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	sort(a+1,a+n+1);
	f[n+1]=mk(0,0);
	for(int i=n;i>=1;i--){
		if(a[i]>=4)f[i]=min(mk(f[i+1].se,f[i+1].fi+a[i]),mk(f[i+1].fi+4,f[i+1].se+a[i]-4));
		else f[i]=mk(f[i+1].se,f[i+1].fi+a[i]);
	}
	printf("%lld %lld\n",f[1].fi,f[1].se);
	return 0;
}

B 【0506 B组】齿轮

如果齿轮 \(x\) 转动会带动 \(y\) 转动的话,设 \(x\) 的转速为 \(v\),可以得到 \(y\) 的转速(不考虑正负)为 \(\dfrac{va_x}{a_y}\),所以路径上齿轮的大小无关紧要。

所以现在我们就需要设计一个算法,能求出:1.两个点是否连通;2.连通块内是否有卡死的齿轮;3.两点间距离的奇偶性。发现 dsu 能完美满足以上需求。你可以写边带权/拓展域。

点击查看代码(边带权)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int gcd(int x,int y){
	return (y==0)?x:gcd(y,x%y);
}
int fa[100005],d[100005],tag[100005];
int find(int x){
	if(x==fa[x])return x;
	int fx=find(fa[x]);
	d[x]^=d[fa[x]];
	return fa[x]=fx;
}
void merge(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy){
		if((d[x]^d[y])!=1)tag[fx]=0;
		return;
	}
	d[fy]=d[y],fa[fy]=y,tag[y]=tag[fy],d[y]=d[x]^1,fa[y]=fx;
	if(tag[y]==0)tag[fx]=0;
}
int check(int x,int y){
	int fx=find(x),fy=find(y);
	if(!tag[fx]||fx!=fy)return 0;
	return ((d[x]^d[y])==0?1:-1);
}
int a[100005];
signed main(){
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	int n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read(),fa[i]=i,d[i]=0,tag[i]=1;
	while(m--){
		int op=read();
		if(op==1){
			int x=read(),c=read();
			a[x]=c;
		}
		else if(op==2){
			int x=read(),y=read();
			if(x==y)continue;
			merge(x,y);
		}
		else {
			int x=read(),y=read(),v=read(),o=check(x,y);
			if(o==0)puts("0");
			else{
				int g=gcd(v*a[x],a[y]);
				printf("%lld/%lld\n",o*v*a[x]/g,a[y]/g);
			}
		}
	}
	return 0;
}
点击查看代码(拓展域)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int gcd(int x,int y){
	return (y==0)?x:gcd(y,x%y);
}
int fa[200005];
int find(int x){
	return (x==fa[x])?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx!=fy)fa[fy]=fx;
}
int a[100005];
signed main(){
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	int n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=2*n;i++)fa[i]=i;
	while(m--){
		int op=read();
		if(op==1){
			int x=read(),c=read();
			a[x]=c;
		}
		else if(op==2){
			int x=read(),y=read();
			if(x==y)continue;
			merge(x,y+n),merge(x+n,y);
		}
		else {
			int x=read(),y=read(),v=read();
			if((find(x)==find(y))^(find(x)==find(y+n))){
				if(find(x)==find(y+n))printf("-");
				int g=gcd(v*a[x],a[y]);
				printf("%lld/%lld\n",v*a[x]/g,a[y]/g);
			}
			else puts("0");
		}
	}
	return 0;
}

C 【0506 B组】排班方案

有一个贪心的错解能过,但是有反例,就不说了。

因为 \(d_i\) 不严格递增,我们可以按顺序安排每个人的工作时间(以别的顺序安排也行,但需要分讨,结果是一样的)。容易得到 \(c=\prod\limits_{i=1}^n(d_i-i+1)\)。令 \(D_i=d_i-i+1\),有 \(D_i\le D_{i+1}+1\),且每个 \(D_i\) 一定是 \(c\) 的因子。

现在,问题转化成了求 \(D_i\),让 \(D_n\) 最小。设 \(D_n=x\)\(\max\limits_{i=1}^n\{D_i\}=y\)。假设方案存在,则从 \(y\)\(D_n\) 中间一定形如 \(y,y-1,y-2\ldots x\),因为你要把 \(y\)\(x\) 连接起来。

假设我们已经确定了 \(x,y\) 的值,令 \(f_{i,j}\) 表示选若干 \(i\) 以内的数,使他们的乘积为 \(j\),最少需要选几个数,\(c'\)\(c\)\(x\)\(y\) 之后的值,这对 \((x,y)\) 合法当且仅当 \(f_{y,c'}+(y-x+1)\le n\)。为什么是 \(\le n\)?因为少的你可以用 1 补齐。

发现 \(x,y,c'\) 都是 \(c\) 的因子,所以计算 \(f_{i,j}\)\(O(d(c)^2)\) 的(枚举 \((x,y)\) 理论上也是 \(O(d(c)^2)\) 的,但有 \(13!=6227020800\),故固定 \(x\),可能合法的 \(y\) 不超过 13 个,所以这里是 \(O(d(c))\) 的)。\(d(c)\)\(c\le 10^9\) 时最大值为 1344,可以通过。

对于构造方案,后面 \(y\)\(x\) 那段就按这个顺序排,前面的凑 \(c'\) 的就从小到大排即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,c,tot,B,d[5005],f[5005][5005],ans[3000005],r[40005];
int Find(int x){
	if(x<=B)return r[x];
	else return tot-r[c/x]+1;
}
void solve(){
	n=read(),c=read(),tot=0,B=(int)sqrt(c);
	for(int i=1;i*i<=c;i++){
		if(c%i)continue;
		d[++tot]=i;
		if(c/i!=i)d[++tot]=c/i;
	}
	sort(d+1,d+tot+1);
	for(int i=1;i<=tot&&d[i]<=B;i++){
		r[d[i]]=i;
	}
	for(int i=0;i<=tot;i++){
		for(int j=0;j<=tot;j++){
			f[i][j]=inf;
		}
	}
	f[0][1]=0;
	for(int i=1;i<=tot;i++){
		for(int j=1;j<=tot;j++){
			f[i][j]=f[i-1][j];
			if(d[j]%d[i]==0)f[i][j]=min(f[i][j],f[i][Find(d[j]/d[i])]+1);
		}
	}
	int X=-1,Y=-1,C=-1;
	for(int x=1;x<=tot;x++){
		ll pro=d[x];
		if(c%pro)continue;
		if(f[x][Find(c/pro)]+1<=n){X=x,Y=x,C=Find(c/pro);break;}
		for(int y=x+1;y<=tot;y++){
			for(int i=d[y-1]+1;i<=d[y];i++){
				pro*=i;
				if(pro>c)break;
			}
			if(pro>c)break;
			if(c%pro)continue;
			if(f[y][Find(c/pro)]+(d[y]-d[x]+1)<=n){X=x,Y=y,C=Find(c/pro);break;}			
		}
		if(X!=-1&&Y!=-1)break;
	}
	if(X==-1&&Y==-1)return puts("GG"),void();
	int all=0,XX=X,YY=Y;
	while(Y>1&&d[C]>1){
		while(d[C]%d[Y]==0&&f[Y][C]==f[Y][Find(d[C]/d[Y])]+1){
			ans[++all]=d[Y],C=Find(d[C]/d[Y]);
		}
		Y--;
	}
	for(int i=all+1;i<=n-(YY-XX+1);i++)ans[++all]=1;
	if(all)sort(ans+1,ans+all+1);
	for(int i=YY;i>=XX;i--)ans[++all]=d[i];
	for(int i=1;i<=n;i++)printf("%d ",ans[i]+i-1);
	puts("");
}
signed main(){
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	int T=read();
	while(T--)solve();
	return 0;
}

D 【0506 B组】简单的数据结构题

发现如果我们固定一个左端点 \(i\),因为区间变大 and 不增,且一旦变小至少会有一个 1 变为 0,那么这些区间的 and 至多有 \(\log A\) 种取值。可以从右往左枚举 \(i\),维护 \(pos_j\) 表示当前经过的所有 \(k\) 中,满足 \(a_k\)\(j\) 位为 0 的最大的 \(k\)。这样我们就在 \(O(n\log A+n\log A\log\log A)\) 的时间内求出了每个 \(i\) 的 and 值的分割点(后面那个是排序 + 去重的时间)。

现在,我们把询问都离线下来,按左端点从大到小排序。从右往左枚举 \(i\),有一些分割点把 \(i\)\(n\) 分成了若干段,以 \(i\) 为左端点,同一段内的若干个下标作为右端点,得到的 and 是相同的。这样,对于每个 \(i\),我们可以在 \(\log A\) 的时间内求出哪些区间的 and 是完全平方数(因为每段中间的数一定不会影响 and 的值)。

现在,我们把那些 and 为完全平方数的区间,以右端点为下标,丢到一颗线段树上维护。对于询问 \([ql,qr]\),我们只用查询线段树上 \([ql,qr]\) 区间的和即可。因为我们以右端点为下标,故会对答案造成贡献的区间 \([l,r]\) 满足 \(r\le qr\)。同时,枚举 \(i\) 的顺序也决定了 \(l\ge ql\),所以这样统计是不重不漏的。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int check(int n){
	int x=(int)sqrt(n);
	return (x*x==n);
}
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		ll s,tag;
	}c[400005];
	void pushup(int p){
		c[p].s=c[ls].s+c[rs].s;
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
		c[ls].s+=c[p].tag*ln,c[rs].s+=c[p].tag*rn;
		c[ls].tag+=c[p].tag,c[rs].tag+=c[p].tag;
		c[p].tag=0;  
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){c[p].s=0;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int L,int R,int k){
		if(L>R)return;
		if(L<=l&&r<=R){c[p].s+=1ll*(r-l+1)*k,c[p].tag+=k;return;}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)update(lson,L,R,k);
		if(R>mid)update(rson,L,R,k);
		pushup(p);
	}
	ll query(int l,int r,int p,int L,int R){
		if(L>R)return 0;
		if(L<=l&&r<=R)return c[p].s;
		int mid=(l+r)>>1;ll res=0;pushdown(l,r,p);
		if(L<=mid)res+=query(lson,L,R);
		if(R>mid)res+=query(rson,L,R);
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int a[100005],pos[30],r[500005];vector<int>v,q[100005];ll ans[500005];
signed main(){
	freopen("D.in","r",stdin);
	freopen("D.out","w",stdout);
	int T=read();
	while(T--){
		int n=read(),m=read();Tr.build(1,n,1);
		for(int o=0;o<30;o++)pos[o]=n+1;
		for(int i=1;i<=n;i++)a[i]=read(),q[i].clear();
		for(int i=1,l;i<=m;i++)l=read(),r[i]=read(),q[l].push_back(i);
		for(int i=n,val;i>=1;i--){
			v.clear(),val=a[i];
			for(int o=0;o<30;o++)if(!((a[i]>>o)&1ll))pos[o]=i;
			for(int o=0;o<30;o++)if(pos[o]!=n+1)v.push_back(pos[o]);
			sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
			for(int j=0;j<(int)v.size();j++){
				if(check(val))Tr.update(1,n,1,((j==0)?i:v[j-1]),v[j]-1,1);
				val&=a[v[j]];
			}
			if(check(val))Tr.update(1,n,1,(v.empty()?i:v.back()),n,1);
			for(auto j:q[i])ans[j]=Tr.query(1,n,1,i,r[j]);
		}
		for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	}
	return 0;
}

C0279 【0630 B组】模拟测试

坤皇高质量模拟赛!

A 【0630 B组】冤

你可以假定 \(\forall i\in[1,n],a_i\ge1\)

唉,被诈骗了。

因为三角形需要满足两边之和大于第三边,显然最优选法是排序后取相邻三个。考虑最坏情况,即所有长度构成斐波那契数列。因为 \(a_i\le10^9\),故长度过大的区间一定合法,剩下的暴力判断即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[1500005],b[105];
signed main(){
	int n=read(),q=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	while(q--){
		int l=read(),r=read(),m=0,fl=0;
		if(r-l+1>50){puts("YES");continue;}
		for(int i=l;i<=r;i++)b[++m]=a[i];
		sort(b+1,b+m+1);
		for(int i=2;i<=m-1;i++)if(b[i-1]+b[i]>b[i+1]){fl=1;break;}
		puts(fl?"YES":"NO");
	}
	return 0;
}

下面说一个歪解。

如果你不知道这个性质,最暴力的方法是直接区间排序判断。这会白白浪费很多时间,我们考虑把询问离线下来莫队。不太能支持删除,所以我们直接莽一个回滚莫队+压位 trie。

这样还是过不去,所以我们考虑一点小小的剪枝:对于左端点在同一块里的询问,如果右边已经找到合法的三元组了,那以后的那些左端点在同一块里的询问也一定是合法的,可以直接跳过。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int inf=1e9; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct Que{
	int l,r,id;
}q[1500005];
int bel[1500005],bl[2005],br[2005];
int cmp(Que x,Que y){
	if(bel[x.l]^bel[y.l])return bel[x.l]<bel[y.l];
	return x.r<y.r;
}
//
#define clz(x) (__builtin_clzll(x))
#define ctz(x) (__builtin_ctzll(x))
ull BUFF[70000005],*BT=BUFF+sizeof(BUFF)/sizeof(ull);
ull *alloc(int siz){return BT-=siz;}
const int g=6,mod=(1<<g)-1;
int dep;ull *c[6];
void init(int siz){
	for(dep=1;;dep++){
		int cnt=(siz+(1ull<<g*dep)-1)>>g*dep;
		c[dep-1]=alloc(cnt);
		if(cnt==1)return;
	}
}	
void insert(int x){
	for(int i=0;i<dep;i++){
		ull p=1ull<<(x>>i*g&mod);
		if(c[i][x>>(i+1)*g]&p)return;
		c[i][x>>(i+1)*g]|=p;
	}
}
void erase(int x){
	for(int i=0;i<dep;i++)
		if(c[i][x>>(i+1)*g]&=~(1ull<<(x>>i*g&mod)))return;
}
int getpre(int x){
	for(int i=0;i<dep;i++){
		int cur=(x>>i*g)&mod;ull v=c[i][x>>(i+1)*g];
		if(v&((1ull<<cur)-1)){
			int res=x>>(i+1)*g<<(i+1)*g;res+=(mod-clz(v&((1ull<<cur)-1)))<<i*g;
			for(int j=i-1;j>=0;j--)res+=(mod-clz(c[j][res>>(j+1)*g]))<<j*g;
			return res;
		}
	}
	return -1;
}	
int getsuf(int x){
	for(int i=0;i<dep;i++){
		int cur=(x>>i*g)&mod;ull v=c[i][x>>(i+1)*g];
		if(v>>cur>1){
			int res=x>>(i+1)*g<<(i+1)*g;res+=(ctz(v>>(cur+1))+cur+1)<<i*g;
			for(int j=i-1;j>=0;j--)res+=ctz(c[j][res>>(j+1)*g])<<j*g;
			return res;
		}
	}
	return -1;
}
int B[1500005],tot,a[1500005],b[1500005];
int get(int x){
	if(b[x]==0){
		return 0;
	}
	if(B[x]>=2){
		return 1;
	}
	else if(B[x]==1){
		int pre=getpre(x),suf=getsuf(x);
		if(pre!=-1&&suf!=-1&&b[pre]+b[x]>b[suf])return 1;
		if(pre!=-1&&b[pre]+b[x]>b[x])return 1;
		if(suf!=-1&&b[x]+b[x]>b[suf])return 1;
		return 0;
	}
	else{
		int pre=getpre(x),suf=getsuf(x);
		if(pre!=-1&&suf!=-1&&b[pre]+b[x]>b[suf])return 1;
		if(pre!=-1){
			int ppre;
			if(B[pre]>1)ppre=pre;
			else ppre=getpre(pre);
			if(ppre!=-1&&b[ppre]+b[pre]>b[x])return 1;
		}
		if(suf!=-1){
			int ssuf;
			if(B[suf]>1)ssuf=suf;
			else ssuf=getsuf(suf);
			if(ssuf!=-1&&b[x]+b[suf]>b[ssuf])return 1;
		}
		return 0;
	}
}
int ask(int l,int r){
	for(int i=l;i<=r;i++){
		if(get(a[i])){
			for(int j=l;j<i;j++){
				B[a[j]]--;
				if(B[a[j]]==0)erase(a[j]);
			}
			return 1;
		}
		B[a[i]]++;
		if(B[a[i]]==1)insert(a[i]);
	}
	for(int i=l;i<=r;i++){
		B[a[i]]--;
		if(B[a[i]]==0)erase(a[i]);
	}
	return 0;
}
int ans[1500005];
signed main(){
//	freopen("bitterness.in","r",stdin);
//	freopen("bitterness.out","w",stdout);
	int n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=b[++tot]=read();
	sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
	init(tot+5);
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
	for(int i=1;i<=m;i++){
		q[i].l=read(),q[i].r=read(),q[i].id=i;
	}
	int siz=(int)sqrt(n),num=(n+siz-1)/siz;
	for(int i=1;i<=n;i++)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=num;i++)bl[i]=(i-1)*siz+1,br[i]=i*siz;
	br[num]=n;
	sort(q+1,q+m+1,cmp);
	for(int i=1;i<=m;i++)if(bel[q[i].l]==bel[q[i].r])ans[q[i].id]=ask(q[i].l,q[i].r);
	for(int i=1,j=1;i<=num;i++){
		int L=br[i]+1,R=br[i],res=0;
		while(j<=m){
			if(bel[q[j].l]!=i)break;
			if(bel[q[j].l]==bel[q[j].r]){j++;continue;}
			if(res){ans[q[j].id]=1;j++;continue;}
			while(R<q[j].r){
				R++,res|=get(a[R]),B[a[R]]++;
				if(B[a[R]]==1)insert(a[R]);
			}
			int tmp=0;
			while(L>q[j].l){
				L--,tmp|=get(a[L]),B[a[L]]++;
				if(B[a[L]]==1)insert(a[L]);
			}
			ans[q[j].id]=(res|tmp),j++;
			while(L<=br[i]){
				B[a[L]]--;
				if(B[a[L]]==0)erase(a[L]);	
				L++;			
			}
		}
		while(R>br[i]){
			B[a[R]]--;
			if(B[a[R]]==0)erase(a[R]);	
			R--;			
		}
	}
	for(int i=1;i<=m;i++)puts((ans[i]?"YES":"NO"));
	return 0;
}

B 【0630 B组】缘

首先这玩意是一个格雷码,但是知道了好像也没什么卵用。

然后 \(u\to v\) 等价于 \(u\oplus v\to 0\)。记 \(t=u\oplus v\),考虑这样一个流程:固定 \(t\) 中一个 1,对剩下的没有固定过的位做格雷码,做了一圈之后再把这一位改成 0,然后重复这个流程。因为格雷码是一个环,所以抛开固定的哪一位,剩下那些位在固定的这位 \(0\to 1\) 的时候只会变一位。所以这个流程可以看做不断地在 \(t\) 中选一个 1,把它变为 0,然后再把另一位取反。这样做显然对 \(t\) 中 1 的数量的奇偶性没有影响,所以我们来分个类。

奇数:你可以先化成一个 1 然后全是 0 的形式,然后你每次做的时候都把一个 0 改成 1,这样就能刚好遍历完 \(2^n\) 个数。

偶数:你可以先化成两个 1 然后全是 0 的形式,然后你每次做的时候都把一个 0 改成 1,这样最后一定是两个 1 的形式,直接 \(11\to 10\to 00\) 刚好遍历 \(2^n-1\) 个数。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int lowbit(int x){
	return x&-x;
}
int checkbit(int x,int y){
	return ((x>>y)&1ll);
}
int popcount(int x){
	int cnt=0;
	while(x)cnt+=(x&1ll),x>>=1;
	return cnt;
}
int safemod(int x,int y,int mod){
	return ((x+y)%mod+mod)%mod;
}
int getmask(int x,int msk){
	int y=0,cnt=0;
	while(msk){
		int o=lowbit(msk);msk^=o;
		y|=((x&o)!=0)<<cnt,cnt++;
	}
	return y;
}
int createmask(int x,int msk){
	int y=0,cnt=0;
	while(msk){
		int o=lowbit(msk);msk^=o;
		y|=o*checkbit(x,cnt),cnt++;
	}
	return y;
}
int g[300005],rnk[300005];vector<int>ans;
signed main(){
	int n=read(),u=read(),v=read(),all=(1ll<<n)-1;u^=v;
	if(u==0)return printf("0\n%lld\n",v),0;
	for(int i=0;i<(1ll<<n);i++)g[i]=i^(i/2),rnk[g[i]]=i;
	int fix=0,ucnt=n;ans.push_back(u);
	while(ucnt>2||(ucnt>0&&popcount(u)%2==1)){
		int p=0;
		while(p<n&&(checkbit(fix,p)||!checkbit(u,p)))p++;
		fix|=(1<<p),ucnt--;
		int k=getmask(u,all^fix),t=(1<<ucnt),d=1;
		if(g[safemod(rnk[k],-1,t)]==0)d=-1;
		for(int i=safemod(rnk[k],d,t);i!=rnk[k];i=safemod(i,d,t)){
			int nxt=(u&fix)|createmask(g[i],all^fix);
			ans.push_back(nxt),u=nxt;
		}
		u^=(1<<p),ans.push_back(u);
	}
	if(ucnt==2&&popcount(u)==2){
		ans.push_back((u&fix)|createmask(1,all^fix));
		ans.push_back((u&fix)|createmask(0,all^fix));
	}
	printf("%lld\n",(int)ans.size()-1);
	for(auto x:ans)printf("%lld ",x^v);
	return 0;
}

C 【0630 B组】愿

首先考虑哪些边是有用的。你发现我们需要保留的边一定是最小生成树,这个你可以从类似 kruskal 加边的角度去推。

我们现在把所有边按边权从小到大排序,依次加边。定义 \(f_{i,j}\) 表示当前枚举到第 \(i\) 条边,使得 \(j\) 所在连通块每个点水位小于 \(w_i\) 的方案数。可以再记录一个 \(h_i\) 表示以 \(i\) 为根的连通块当前的水位限制是多少。因为每条边只会改变两个连通块的答案,所以第一维可以省略。转移式很简单,这里不写了。

对于那些没有与 1 相连的连通块,它们只对方案数有影响,这就是一个乘法原理,最后答案乘一下即可。对于那些与 1 节点相连的点,这个跟刚刚 dp 转移的相似。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353,i2=(mod+1)/2;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int fa[100005],h[100005],f[100005];
int find(int x){
	return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
struct edge{
	int u,v,w;
}e[200005];
int cmp(edge x,edge y){
	return x.w<y.w;
}
signed main(){
	int n=read(),m=read(),d=read(),ans=0;
	for(int i=1;i<=n;i++)fa[i]=i,h[i]=1,f[i]=0;
	for(int i=1,u,v,w;i<=m;i++){
		u=read(),v=read(),w=read(),e[i]=(edge){u,v,w};
	}
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++){
		int u=find(e[i].u),v=find(e[i].v),w=e[i].w;
		if(u==v)continue;
		if(u==find(1)||v==find(1)){
			if(v==find(1))swap(u,v);
			ans=1ll*(ans+1ll*(w-1+h[u])%mod*(w-h[u])%mod*i2%mod)%mod*(f[v]+w-h[v])%mod,h[u]=w,fa[v]=u;
			continue;
		}
		f[u]=1ll*(f[u]+w-h[u])%mod*(f[v]+w-h[v])%mod,h[u]=w,fa[v]=u;
	}
	ans=1ll*(ans+1ll*(d+h[find(1)])%mod*(d-h[find(1)]+1)%mod*i2%mod)%mod;
	for(int i=1;i<=n;i++){
		if(find(i)!=find(1)&&find(i)==i)ans=1ll*ans*((f[i]+(d-h[i]+1))%mod)%mod;
	}
	printf("%d\n",ans);
	return 0;
}

D 【0630 B组】怨

因为数区间不好做,我们数端点,数量除以 2 就是答案。考虑把所有询问离线下来,从小到大枚举询问的右端点 \(i\)。在枚举的同时,把每个序列中的元素区间看做对数轴的一个覆盖,用 ODT 维护一下数轴上每个位置最后一次被覆盖的时间戳。如果有相邻的两个区间时间戳分别为 \(x\)\(y\)(设 \(x<y\)),那么所有形如 \((j,i)\) 的满足 \(j\in (x,y]\) 的询问都会在这两个区间的交界处找到端点。你可以用 BIT 维护这个区间加和单点差。

当然,实际写起来你会发现因为区间是连续的,普通的 ODT 写法非常【数据删除】,所以你可以把维护信息改为不同时间戳交接的位置,以及左右区间的时间戳。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,SIZ=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,l[400005],r[400005],ql[1000005],qr[1000005];
vector<int>v[400005];
struct BIT{
	int c[1000005];
	void build(){
		for(int i=1;i<=n;i++)c[i]=0;
	}
	void add(int x,int k){
		for(;x<=n;x+=x&-x)c[x]+=k;
	}
	void add(int l,int r,int k){
		if(l>r)return;
		add(l,k),add(r+1,-k);
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
}Tr;
struct Node{
	int p,lc,rc;
	bool operator <(const Node &o)const{
		return p<o.p;
	}
};
set<Node>s;
void assign(int l,int r,int v){
	auto itl=s.lower_bound((Node){l,0,0}),itr=s.lower_bound((Node){r+1,0,0});
	int lc=prev(itl)->rc,rc=itr->lc;
	for(auto it=itl;it!=itr;it++)Tr.add(min(it->lc,it->rc)+1,max(it->lc,it->rc),-1);
	s.erase(itl,itr);s.insert((Node){l,lc,v}),s.insert((Node){r,v,rc});
	Tr.add(min(lc,v)+1,max(lc,v),1);Tr.add(min(v,rc)+1,max(v,rc),1);
}
int ans[1000005];
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)l[i]=read(),r[i]=read();
	for(int i=1;i<=m;i++)ql[i]=read(),qr[i]=read(),v[qr[i]].push_back(i);
	s.insert((Node){0,0,0}),s.insert((Node){SIZ+1,0,0});Tr.build();
	for(int i=1;i<=n;i++){
		assign(l[i],r[i],i);
		for(auto x:v[i])ans[x]=Tr.ask(ql[x]);
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]/2);
	return 0;
}
posted @ 2023-06-17 21:50  xx019  阅读(24)  评论(0)    收藏  举报