ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2021ZR noip集训day12

2021-11-02 22:02:43  阅读:204  来源: 互联网

标签:fir noip int tot 2021ZR fa long day12 mod


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\)

标签:fir,noip,int,tot,2021ZR,fa,long,day12,mod
来源: https://www.cnblogs.com/suxxsfe/p/15501399.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有