2025国庆Day4

模拟赛

T1

简单做法:

发现本题所有运算全是加法

直接记录c,s之和

转移即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
const int MOD=998244353;
int dp[1005][1005][2];
int a[1005][1005],c[1005][1005][2],sum[1005][1005][2];
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int n=read(),m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			char s;
			cin>>s;
			if(s=='.') a[i][j]=1;
		}
	}
	sum[1][1][0]=sum[1][1][1]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i-1][j]){
				jiaa(sum[i][j][0],(sum[i-1][j][0]+sum[i-1][j][1])%MOD);
				jiaa(c[i][j][0],(c[i-1][j][0]+sum[i-1][j][0]*(i*j)%MOD+sum[i-1][j][1]*(i*j)%MOD)%MOD);
				jiaa(dp[i][j][0],(dp[i-1][j][0]+c[i-1][j][0]+sum[i-1][j][0]*(i*j)%MOD+dp[i-1][j][1]+sum[i-1][j][1]*(i*j)%MOD)%MOD);
			}
			if(a[i][j-1]){
				jiaa(sum[i][j][1],(sum[i][j-1][0]+sum[i][j-1][1])%MOD);
				jiaa(c[i][j][1],(c[i][j-1][1]+sum[i][j-1][1]*(i*j)%MOD+sum[i][j-1][0]*(i*j)%MOD)%MOD);
				jiaa(dp[i][j][1],(dp[i][j-1][1]+c[i][j-1][1]+sum[i][j-1][1]*(i*j)%MOD+dp[i][j-1][0]+sum[i][j-1][0]*(i*j)%MOD)%MOD);
			}
		}
	}
	if(!a[n][m]){
		cout<<0<<'\n';
		return 0;
	}
	int ans=(dp[n][m][0]+dp[n][m][1])%MOD;
	ans=ans*ksm(2,MOD-2,MOD)%MOD;
	cout<<ans<<'\n';
	return 0;
}

高级做法:

按拐点dp

前缀和优化

实现较为麻烦

T2

可以建出虚树

直接跑dfs求树的重心即可

或者观察:

点集的重心⼀定是“dfs序中位数”的祖先

此处,若点集⼤⼩为奇数,“dfs序中位数”即为点集按 dfs 序排序后,最中间的点

若为偶数,排序后最中间的点有两个,重⼼⼀定是它们之⼀的祖先

通过倍增的⽅法找到重⼼

具体地,可以发现重心⼀定是“中位数”的所有祖先(包括“中位数”本⾝)中,满⾜“条件1”的、深度最深的⼀个

若点集大小为偶数,对中间两个点分别找一次重心

取dep较大的即可

带权做法类似

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
#define lowbit(x) x&(-x)
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
vector<int> tu[100005];
int dep[100006],f[100005][25],dfn[100005],cnt,siz[100005],sum[100005];
pair<int,int> dian[100005];
void dfs(int x,int fa){
	dfn[x]=++cnt;
	dep[x]=dep[fa]+1;
	siz[x]=1;
	for(int i=0;i<=20;i++){
		f[x][i+1]=f[f[x][i]][i];
	}
	for(auto ed:tu[x]){
		if(ed==fa) continue;
		f[ed][0]=x;
		dfs(ed,x);
		siz[x]+=siz[ed];
	}
}
void modify(int x,int y){
	for(int i=x;i<=100000;i+=lowbit(i)){
		sum[i]+=y;	
	}
}
int query(int x){
	int ans=0;
	for(int i=x;i;i-=lowbit(i)){
		ans+=sum[i];
	}
	return ans;
}
int get(int l,int r){
	return query(r)-query(l-1);
}
int ans(int x,int k){
	int su=get(dfn[x],dfn[x]+siz[x]-1);
	if(su>k/2) return x;
	for(int i=20;i>=0;i--){
		int fa=f[x][i];
		if(fa&&get(dfn[fa],dfn[fa]+siz[fa]-1)<=k/2) x=fa;
	}
	return f[x][0];
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int n=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		tu[u].push_back(v);
		tu[v].push_back(u);
	}
	dfs(1,0);
	int q=read();
	while(q--){
		int k=read();
		for(int i=1;i<=k;i++){
			dian[i].second=read();
			dian[i].first=dfn[dian[i].second];
			modify(dian[i].first,1);
		}
		sort(dian+1,dian+k+1);
		int mid=k>>1;
		cout<<ans(dian[mid+1].second,k)<<'\n';
		for(int i=1;i<=k;i++){
			modify(dian[i].first,-1);
		}
	}
	return 0;
}

T3

将偶数的边连两次,奇数的边连一次

找欧拉路径即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;
}
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void out(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10)putchar(x+'0');
	else out(x/10),putchar(x%10+'0');
}
vector<pair<int,int> > tu[500005];
int n,m,now[500005],st[500005],vis[500005],top;
int du[500005];
void dfs(int x){//有向图和无向图通用写法
	for(int i=now[x];i<tu[x].size();i=now[x]){
		now[x]=i+1;
		if(vis[tu[x][i].second]) continue;
		vis[tu[x][i].second]=1;
		dfs(tu[x][i].first);
	}
	st[++top]=x;
}
signed main(){
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	n=read(),m=read();
	int cnt=0;
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),t=read();
		if(!t){
			tu[v].push_back({u,++cnt});
			tu[u].push_back({v,cnt});
			du[u]++;
			du[v]++;
		}
		tu[v].push_back({u,++cnt});
		tu[u].push_back({v,cnt});
		du[u]++;
		du[v]++;
	}
	int qi=1;
	int f=0;
	for(int i=1;i<=n;i++){
		if(du[i]%2==1){
			qi=i;
			f++;
		}
	}
	if(f>2||f==1){
		cout<<-1<<'\n';
		return 0;
	}
	for(int i=1;i<=n;i++){
		sort(tu[i].begin(),tu[i].end());
	} 
	dfs(qi);
	cout<<top<<'\n';
	for(int i=top;i;i--){
		cout<<st[i]<<' ';
	}
	return 0;
}

T4

数学!

容斥原理

image

image

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
const int MOD=1e9+7;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int fac[1000006],infac[1000006],inv[1000006];
int C(int n,int m){
	return fac[n]*infac[m]%MOD*infac[n-m]%MOD;
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int N=1000000;
	fac[0]=1;
	for(int i=1;i<=N;i++){
		fac[i]=fac[i-1]*i%MOD;
		fac[i]%=MOD;
	}
	infac[N]=ksm(fac[N],MOD-2,MOD);
	for(int i=N-1;i>=0;i--){
		infac[i]=infac[i+1]*(i+1)%MOD;
	}
	int n=read(),m=read(),k=read();
	int ff=1,ans=0;
	for(int i=m;i>=0;i--){
		int xs=1;
		if(i%2==1) xs=-1;
		ans+=C(m,i)*xs*ksm(ff-1,n,MOD)%MOD;
		ans%=MOD;
		ff=(2*ff-C(m-i,k))%MOD;
	}
	ans+=MOD;
	ans%=MOD;
	cout<<ans<<'\n';
	return 0;
}

图论

最小生成树

kruskal prim

brouvka:每次取每个点的最小出边,合并,直到只剩一个点

最短路

https://www.luogu.com.cn/problem/P4568

分层图

https://www.luogu.com.cn/problem/UVA1151

暴力用哪个套餐

每次套餐内的点合并,跑prim

复杂度O(n^2 * 2^q)小常数2e8能过

https://www.luogu.com.cn/problem/P1396

求最小瓶颈路

易证一定是最小生成树上的路径

若多测树上倍增即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
	if(b==0) return 1;
	if(b==1) return a%p;
	int c=ksm(a,b/2,p);
	c=c*c%p;
	if(b%2==1) c=c*a%p;
	return c%p;	
}
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int f[10005];
struct tree_edge{
	int l,r,w;
}edge[20005];
bool cmp(tree_edge i,tree_edge j){
	return i.w<j.w;
}
int find(int x){
	if(f[x]==x) return x;
	return f[x]=find(f[x]);
}
void merge(int x,int y){
	f[find(x)]=find(y);
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	int n=read(),m=read(),s=read(),t=read();
	for(int i=1;i<=m;i++){
		cin>>edge[i].l>>edge[i].r>>edge[i].w;
	}
	sort(edge+1,edge+m+1,cmp);
	for(int i=1;i<=n;i++){
		f[i]=i;
	}
	for(int i=1;i<=m;i++){
		if(find(edge[i].l)!=find(edge[i].r)){
			merge(edge[i].l,edge[i].r);
		}
		if(find(s)==find(t)){
			cout<<edge[i].w<<'\n';
			return 0;
		}
	}
	return 0;
}

https://www.luogu.com.cn/problem/P3008

是不是可以SPFA

对所有的双向边联通块缩点

一定是dag

拓扑排序更新dis

欧拉路径

欧拉路径存在,当且仅当图是联通图,且度数为奇数的点的个数不超过 2

  1. 如果度数为奇数的点个数为 0,则欧拉路⼀定是欧拉回路:即起点和终点相同

  2. 如果度数为奇数的点个数为 2,则欧拉路径必定从它们中的⼀个出发,并在另⼀个终⽌

构造方案将起点终点连边,求欧拉回路即可

若没有欧拉回路,如何构建?

容易发现奇度数的点一定是偶数个

将这些奇度数的点两两连边即可

二分图

二分染色当且仅当不存在奇环

联通分量

image

并查集维护到fa的路径奇偶性

若su^sv==w%2就存在答案

注意若联通块内存在奇环,一定有答案

CF547D

对每个点,横纵坐标连边

形成的图强制补成有欧拉回路的图

跑欧拉回路即可

(欧拉回路定向题,从x->y染红,反之染蓝)

image

T3 加强版

对于所有奇度数的点连边

边权是原图的最短路

对新图进行最小匹配

树上问题

树的重心

点集合并

image

例:区间询问,求区间内的点的重⼼

可持久化线段树+二分求出dfs序上中位数

复杂度O(nlog^2n)

树的直径

点集合并

新直径一定是原来4个直径端点的2个

树的中心

任意直径的中点

一定唯一

为根时任意一子树深度<=直径的一半

LCA

image

点集的LCA一定是dfs序最大和最小的lca

树上差分

点集加:

dfs序排序

相邻节点lca -w

点集里的点 +w

所有点的lca -w

最后子树和

遍历序

dfs序(子树拍扁)

bfs序(所有儿子拍扁)

欧拉序(lca)

括号序:

往下走(,往上走)

查询两个点,将括号序列能匹配的消除,剩余括号数量即为两点间距离

剩余括号一定是几个)+几个(,右括号的个数即为到lca的距离(左括号同理)

可线段树维护

可支持子树平移

image

注意第二行的数据结构基本都是静态查询,即修改和查询分开

posted @ 2025-10-05 17:13  gbrrain  阅读(14)  评论(0)    收藏  举报