2021ZR noip集训day12

http://zhengruioi.com/contest/1030
\(51+100+0+0=151\)
T1 没预处理二的次幂直接飞了

A.数树

对于一棵没有边权的树,树上两个点的距离定义为两点间路径经过的边数。
现在告诉你一棵有 \(n\) 个点的树和 \(q\) 个询问,第 \(i\) 个询问给出 \(d_i\),请问树上有多少个点集 \(S\) 满足集合内最远的两个点之间距离为 \(d_i\),因为答案可能太大,请对 \(10^9+7\) 取模。
\(q<n\le 2000\)

考虑枚举点集直径的中心
然后枚举深度,要求这个深度的点至少在两个子树中分别出现至少一个,其他的随便选
对于 \(d_i\) 是奇数可以每条边中间加一个虚点
预处理二的次幂,\(O(n^2)\)

#define mod 1000000007
#define N 4006
#define M 8006
long long power[N];
struct Graph{
	int fir[N],nex[M],to[M],tot=1;
	inline void add(int u,int v,int flag=1){
		to[++tot]=v;
		nex[tot]=fir[u];fir[u]=tot;
//			if(flag) printf("	%d %d\n",u,v);
		if(flag) add(v,u,0);
	}
	inline void clear(){std::memset(fir,0,sizeof fir);tot=0;}
}G;
int n;
long long all[N],cnt[M][N];
long long allSum[N],cntSum[M][N];
void dfs(int u,long long *cnt,int fa=0,int deep=0){
	cnt[deep]+=(u<=n);all[deep]+=(u<=n);
	for(int i=G.fir[u];i;i=G.nex[i])if(G.to[i]^fa) dfs(G.to[i],cnt,u,deep+1);
}
long long f[N];
inline void calc(int u,int op){
	std::memset(all,0,sizeof all);
	for(int i=G.fir[u];i;i=G.nex[i]){
		dfs(G.to[i],cnt[i],u,1);
		for(int d=1;d<=n<<1;d++) cntSum[i][d]=cntSum[i][d-1]+cnt[i][d];
	}
	all[0]=allSum[0]=(u<=n);
	for(int d=1;d<=n<<1;d++) allSum[d]=allSum[d-1]+all[d];
	if(op==1){
		for(int d=4;d<n<<1;d+=4){
			long long now=power[allSum[d>>1]];
			now=(now-power[allSum[(d>>1)-1]]+mod)%mod;
			for(int i=G.fir[u];i;i=G.nex[i]){
				long long sub=power[allSum[(d>>1)-1]-cntSum[i][(d>>1)-1]]*((power[cntSum[i][d>>1]]-power[cntSum[i][(d>>1)-1]]+mod)%mod)%mod;
				now=(now-sub+mod)%mod;
			}
			f[d>>1]=(f[d>>1]+now)%mod;
		}
	}
	else{
		for(int d=2;d<n<<1;d+=4){
			long long now=power[allSum[d>>1]];
			now=(now-power[allSum[(d>>1)-1]]+mod)%mod;
			for(int i=G.fir[u];i;i=G.nex[i]){
				long long sub=power[allSum[(d>>1)-1]-cntSum[i][(d>>1)-1]]*((power[cntSum[i][d>>1]]-power[cntSum[i][(d>>1)-1]]+mod)%mod)%mod;
				now=(now-sub+mod)%mod;
			}
			f[d>>1]=(f[d>>1]+now)%mod;
		}
	}
}
int main(){
//		freopen("a1.in","r",stdin);
	n=read();
	for(int i=1;i<n;i++) G.add(read(),i+n),G.add(read(),i+n);
	power[0]=1;for(int i=1;i<=n<<1;i++) power[i]=power[i-1]*2%mod;
	for(int i=1;i<=n;i++) calc(i,1);
	for(int i=n+1;i<n<<1;i++) calc(i,0);
	int q=read();while(q--) writeEN(f[read()]);
	return SUC_RETURN;
}

B.下棋

一个 \(n\times n\) 的棋盘上有 \(m\) 个马和 \(k\) 个兵,小正和小睿在玩游戏。
小正先手,小睿后手,每一手可以移动某一个马,移动的规则按照象棋规则(在某一维度上移动 \(2\) 个单位,另一维度移动 \(1\) 个单位)。移动后的马不能移到棋盘外面,也不能和其他马和兵重合。
第一个让局面出现重复的人输掉游戏。两个局面 \(A\) 和 \(B\) 不重复,当且仅当存在某个位置 \((p,q)\),局面 A 的 \((p,q)\) 有棋子,局面 \(B\) 的 \((p,q)\) 没有棋子。注意:两个马之间是没有差别的。
现在告诉你每个兵的位置,而马的位置未知,请问有多少个初始局面能让小正必胜?
\(n\le 15,1\le m\le 2\)

考虑对所有的局面之间的转移关系建图,这样是一个二分图:

  • 对于 \(m=1\),可以按照 \(x+y\) 的奇偶性染色
  • 对于 \(m=2\),可以按照 \(x+y\) 和 \(p+q\) 的奇偶性是否相同染色

若存在某种最大匹配,使得一个点不是匹配点,则一定不能以他作为起点:先手必定走到一个匹配点上,然后后手走匹配边,先手非匹配边,一定是以匹配边结束从而先手无路可走
于是就变成了二分图最大匹配必须点的问题:https://www.cnblogs.com/suxxsfe/p/15414767.html
点数边数都是 \(O(n^{2m})\),于是复杂度 \(O(n^{3m})\)

#define N 60006
#define M 2000006
struct Graph{
	int fir[N],nex[M],to[M],tot=1;
	int w[M],fir_[N];
	inline void add(int u,int v,int d,int flag=1){
		to[++tot]=v;w[tot]=d;
		nex[tot]=fir[u];fir[u]=tot;
//			if(flag) printf("	%d %d\n",u,v);
		if(flag) add(v,u,0,0);
	}
	inline void clear(){std::memset(fir,0,sizeof fir);tot=0;}
}G,H;
const int di[]={-2,-1,1,2,2,1,-1,-2};
const int dj[]={1,2,2,1,-1,-2,-2,-1};
int n,m,k;
int id[22][22],no[22][22];
inline int check(int i,int j){return i>=1&&j>=1&&i<=n&&j<=n;}
inline int getId(int i,int j){return (i-1)*id[0][0]+j;}
int S,T;
int deep[N];
int left,right,que[N];
inline int bfs(){
	std::memset(deep,0,(T+1)*sizeof deep[0]);
	left=right=0;
	que[0]=S;deep[S]=1;
	int u;
	while(left<=right){
		u=que[left++];
		for(int v,i=G.fir[u];i;i=G.nex[i]){
			v=G.to[i];
			if(deep[v]||!G.w[i]) continue;
			deep[v]=deep[u]+1;que[++right]=v;
			if(v==T) return 1;
		}
	}
	return 0;
}
int dfs(int u,int now=INT_INF){
	if(u==T) return now;
	long long res=now;
	for(int v,&i=G.fir_[u];i;i=G.nex[i]){
		v=G.to[i];
		if(deep[v]!=deep[u]+1||!G.w[i]) continue;
		int k=dfs(v,lib::min(res,G.w[i]));
		if(!k) deep[v]=0;
		else G.w[i]-=k,G.w[i^1]+=k,res-=k;
		if(!res) break;
	}
	return now-res;
}
inline int dinic(){
	int ans=0;
	while(bfs()){
		int now;
		std::memcpy(G.fir_,G.fir,(T+1)*sizeof G.fir[0]);
		while(now=dfs(S)) ans+=now;
	}
	return ans;
}
inline void build(){
	if(m==1){
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]){
			if((i+j)&1) G.add(S,id[i][j],1);
			else G.add(id[i][j],T,1);
		}
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]&&((i+j)&1)){
			for(int i_,j_,k=0;k<8;k++){
				i_=i+di[k];j_=j+dj[k];
				if(!check(i_,j_)||no[i_][j_]) continue;
				G.add(id[i][j],id[i_][j_],1);
			}
		}
	}
	else if(m==2){
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j])for(int x=1;x<=n;x++)for(int y=1;y<=n;y++)if(!no[x][y]&&(x!=i||y!=j)){
			if(((i+j)&1)==((x+y)&1)) G.add(S,getId(id[i][j],id[x][y]),1);
			else G.add(getId(id[i][j],id[x][y]),T,1);
		}
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j])for(int x=1;x<=n;x++)for(int y=1;y<=n;y++)if(!no[x][y]&&(((i+j)&1)==((x+y)&1))&&(x!=i||y!=j)){
			for(int i_,j_,x_,y_,k=0;k<8;k++){
				i_=i+di[k];j_=j+dj[k];x_=x;y_=y;
				if(!check(i_,j_)||no[i_][j_]) goto NO;
				G.add(getId(id[i][j],id[x][y]),getId(id[i_][j_],id[x_][y_]),1);
			NO:
				i_=i;j_=j;x_=x+di[k];y_=y+dj[k];
				if(!check(x_,y_)||no[x_][y_]) continue;
				G.add(getId(id[i][j],id[x][y]),getId(id[i_][j_],id[x_][y_]),1);
			}
		}
	}
}
int color[N],vis[N],noNeed[N];
inline void rebuild(int k){
	std::memset(color,0,sizeof color);std::memset(vis,0,sizeof vis);
	for(int u,i=G.fir[k?T:S];i;i=G.nex[i]){
		u=G.to[i];
		if(G.w[i]^k) noNeed[u]=1;
		color[u]=1;
		for(int v,j=G.fir[u];j;j=G.nex[j]){
			v=G.to[j];
			if(G.w[j]^k) H.add(u,v,0,0);//no match
			else H.add(v,u,0,0);
		}
	}
}
void dfs2(int u){
	if(vis[u]) return;
	vis[u]=1;if(color[u]) noNeed[u]=1;
	for(int i=H.fir[u];i;i=H.nex[i]) dfs2(H.to[i]);
}
inline void work(){
	rebuild(1);//0=left, 1=right
	for(int i=G.fir[T];i;i=G.nex[i])if(!G.w[i]) dfs2(G.to[i]);
	H.clear();
	rebuild(0);
	for(int i=G.fir[S];i;i=G.nex[i])if(G.w[i]) dfs2(G.to[i]);
}
int main(){
	n=read();m=read();k=read();
	for(int x,i=1;i<=k;i++) x=read(),no[x][read()]=1;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]) id[i][j]=++id[0][0];
	if(m==1) S=id[0][0]+1,T=S+1;
	else if(m==2) S=id[0][0]*id[0][0]+1,T=S+1;
	build();
//		printf("%d\n",G.tot);
	dinic();
	work();
	int ans=0;
	if(m==1){
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]&&!noNeed[id[i][j]]) ans++;
	}
	else if(m==2){
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]){
			for(int x=1;x<=n;x++)for(int y=1;y<=n;y++)if(!no[x][y]&&!noNeed[getId(id[i][j],id[x][y])]&&(x!=i||y!=j)) ans++;
		}
		ans>>=1;
	}
	writeEN(ans);
	return SUC_RETURN;
}

C.走路

有一棵 \(n\) 个点的树,树边有边权。
在此基础上,树上被添加了一些重边(添加的边连接的点在原树内也被一条边直接连通)。
给出 \(Q\) 个询问,每个询问的内容是:询问对于一对点 \(p,q\) 从 \(p\) 走到 \(q\) ,不经过重复的点,最多切换多少次边权。路径上,相邻的边如果边权不同,就切换了一次边权。
\(n\le 5\times 10^5,m\le 10^6\)

一个想法是树上倍增处理出 \(u\) 往上 \(2^i\) 会最多切换多少次,但你发现还需要记录两边是啥不然无法合并
这样的话可能的组合非常多就炸了

但是如果两边边权的组合超过三种,则两边都一定会发生切换,所以只记录三种即可
\(O(n\log n)\)

#define N 500006
#define M 2000006
struct Graph{
	int fir[N],nex[M],to[M],tot=1;
	inline void add(int u,int v,int flag=1){
		to[++tot]=v;
		nex[tot]=fir[u];fir[u]=tot;
		if(flag) add(v,u,0);
	}
	inline void clear(){__builtin_memset(fir,0,sizeof fir);tot=0;}
}G;
struct Node{
	int ans,size,s[3],t[3];
	inline void clear(){__builtin_memset(this,0,sizeof(Node));}
	inline Node operator + (const Node &o){
		if(!size) return o;
		if(!o.size) return *this;
		Node ret;ret.clear();
		for(int i=0;i<size;i++)for(int j=0;j<o.size;j++)if(t[i]^o.s[j]){
			ret.s[ret.size]=s[i];ret.t[ret.size]=o.t[j];ret.size++;
			ret.ans=ans+o.ans+1;
			if(ret.size==3) goto FULL;
		}
	FULL:
		if(!ret.size){
			ret.size=1;ret.s[0]=s[0];ret.t[0]=o.t[0];
			ret.ans=ans+o.ans;
		}
		return ret;
	}
	inline void insert(int o){
		if(size==3) return;
		s[size]=o;t[size++]=o;
	}
	inline void operator = (const Node &o){__builtin_memcpy(this,&o,sizeof(Node));}
};
#define MAX 20
Node ans[22][N];
int fa[22][N],deep[N];
std::vector<int>edge[N];
void pre(int u){
	for(int v,i=G.fir[u];i;i=G.nex[i]){
		v=G.to[i];
		if(v==fa[0][u]||fa[0][v]) continue;
		fa[0][v]=u;
		pre(v);
	}
}
void dfs(int u){
	deep[u]=deep[fa[0][u]]+1;
	for(int v,i=G.fir[u];i;i=G.nex[i]){
		v=G.to[i];
		if(v==fa[0][u]||deep[v]) continue;
		fa[0][v]=u;
		for(int o:edge[v]) ans[0][v].insert(o);
		for(int j=1;j<MAX;j++){
			fa[j][v]=fa[j-1][fa[j-1][v]];
			ans[j][v]=ans[j-1][v]+ans[j-1][fa[j-1][v]];
		}
		dfs(v);
	}
}
inline int calc(int x,int y){
	if(x==y) return 0;
	if(deep[x]<deep[y]) lib::swap(x,y);
	Node A,B;A.clear();B.clear();
	for(int i=MAX-1;~i;i--)if(deep[fa[i][x]]>=deep[y]) A=A+ans[i][x],x=fa[i][x];
	if(x==y) return A.ans;
	for(int i=MAX-1;~i;i--)if(fa[i][x]^fa[i][y]) A=A+ans[i][x],B=B+ans[i][y],x=fa[i][x],y=fa[i][y];
	A=A+ans[0][x];B=B+ans[0][y];
	for(int i=0;i<A.size;i++)for(int j=0;j<B.size;j++)if(A.t[i]^B.t[j]) return A.ans+B.ans+1;
	return A.ans+B.ans;
}
int n,m;
int u[M],v[M],w[M];
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		u[i]=read();v[i]=read();w[i]=read();
		G.add(u[i],v[i]);
	}
	pre(1);
	for(int i=1;i<=m;i++){
		if(fa[0][u[i]]==v[i]) edge[u[i]].push_back(w[i]);
		else edge[v[i]].push_back(w[i]);
	}
	dfs(1);
	int q=read();while(q--){
		int x=read(),y=read();
		writeEN(calc(x,y));
	}
	return SUC_RETURN;
}

D.开环

有一串项链,上面有 \(n\) 个珠子,每个珠子上写了一个字母。
请你选择一个位置把项链破开,变成一条链,我们假设破开后的先后顺序和输入的先后顺序都是顺时针。
对于得到的这条链,请你把它分为 \(k\) 个连续的部分,满足这些部分中字典序最大的那个字典序最小。请你输出字典序最大的那个部分。
\(n\le 5\times 10^5\)

上一篇:RHCSA_DAY12


下一篇:总之就是 | ZROI NOIP21冲刺 Day12