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
数学!
容斥原理


#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
-
如果度数为奇数的点个数为 0,则欧拉路⼀定是欧拉回路:即起点和终点相同
-
如果度数为奇数的点个数为 2,则欧拉路径必定从它们中的⼀个出发,并在另⼀个终⽌
构造方案将起点终点连边,求欧拉回路即可
若没有欧拉回路,如何构建?
容易发现奇度数的点一定是偶数个
将这些奇度数的点两两连边即可
二分图
二分染色当且仅当不存在奇环
联通分量

并查集维护到fa的路径奇偶性
若su^sv==w%2就存在答案
注意若联通块内存在奇环,一定有答案
CF547D
对每个点,横纵坐标连边
形成的图强制补成有欧拉回路的图
跑欧拉回路即可
(欧拉回路定向题,从x->y染红,反之染蓝)

T3 加强版
对于所有奇度数的点连边
边权是原图的最短路
对新图进行最小匹配
树上问题
树的重心
点集合并

例:区间询问,求区间内的点的重⼼
可持久化线段树+二分求出dfs序上中位数
复杂度O(nlog^2n)
树的直径
点集合并
新直径一定是原来4个直径端点的2个
树的中心
任意直径的中点
一定唯一
为根时任意一子树深度<=直径的一半
LCA

点集的LCA一定是dfs序最大和最小的lca
树上差分
点集加:
dfs序排序
相邻节点lca -w
点集里的点 +w
所有点的lca -w
最后子树和
遍历序
dfs序(子树拍扁)
bfs序(所有儿子拍扁)
欧拉序(lca)
括号序:
往下走(,往上走)
查询两个点,将括号序列能匹配的消除,剩余括号数量即为两点间距离
剩余括号一定是几个)+几个(,右括号的个数即为到lca的距离(左括号同理)
可线段树维护
可支持子树平移

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

浙公网安备 33010602011771号