CSP-S 模拟赛 44

CSP-S 模拟赛 44

rnk20,5+0+11+30=46

小技巧:

  1. 做差是找规律的常见手法。如果一遍不行,就再做一遍。
  2. 题目中没有对应数据点的特殊性质没有必要去打,除非它的代码极其简单。

T1 岛屿

找规律题。

首先两种特殊情况的部分分是好找的,一种就是调和级数,另一种是变相的调和级数,然后 40pts 就拿到了。

正解是抽象的。易得初始存在的边形成一个完美匹配,且新加的边也必须形成一个完美匹配。所以最后图中每个点的度数一定都为 2,即最后每个连通块必然是个大小至少为 2 的环。

考虑连边过程,连边过程中形成的一定是若干条链和若干个环。只有链对后续有影响,然后按照题解说的搞一下就出来了。代码就是一个公式:

ans=a=1X12a1+b=1Y12X+b .

#include<bits/stdc++.h>
using namespace std;

using ld=long double;
constexpr int MAXN=6e6+5;
int x,y;
ld ans;

int main(){
	freopen("island.in","r",stdin);
	freopen("island.out","w",stdout);
	scanf("%d%d",&x,&y);
	for(int i=1;i<=x;++i)ans+=1.0l/(2*i-1);
	for(int i=1;i<=y;++i)ans+=1.0l/(2*x+i);
	printf("%.16Lf\n",ans);
	return 0;
}

T2 最短路

易得预计 20pts、实测 40pts 的暴力:先跑最短路,然后对于每个点手动 ban 掉最后一条最短路边,然后再跑一遍最短路,复杂度 O(nmlogm)

正解前置芝士:最短路树

构造最短路树,注意不用真的构造,只需保存节点深度和父亲。我们思考如果一个点到它父亲的边被 ban 了怎么办,结果是我们需要一条边连接该点子树内和子树外的任意一节点。

很难处理,那我们转换角度:对于任意一条非树边,它能给哪些点做贡献呢?

设当前节点为 k,则对于原本 1k 的最短路变成 1uvk,将该式转化为:

1uvk=(uv)+(u1)+(v1)(k1)

其中 uv 是枚举的非树边边权,k1 是定值,剩余部分都只和当前的非树边有关,故我们可以针对每一条非树边算出这个式子。每增加一条非树边,该非树边在树上会形成一个环,对于环上的每一个点 i,可以选取的答案是 Wdisi,接下来就有三种方法:

  1. 暴跳。理论复杂度 O(nm),但只是理论。
  2. 树剖维护,理论复杂度 O(nlog2n)
  3. 并查集路径压缩维护。
#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
using namespace std;

char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf,str[20<<2];
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
template<typename T>
void write(T x,char sf='\n'){
	if(x<0)putchar('-'),x=~x+1;
	int top=0;
	do str[top++]=x%10,x/=10;while(x);
	while(top)putchar(str[--top]+48);
	if(sf^'#')putchar(sf);
}
using ll=long long;
using pii=pair<int,int>;
constexpr int MAXN=2e5+5;
constexpr ll INF=0x3f3f3f3f3f3f3f3fll;
int n,m,head[MAXN],tot;
struct{int v,to;ll w;}e[MAXN<<1];
void addedge(int u,int v,ll w){
	e[++tot]={v,head[u],w};
	head[u]=tot;
}
ll dis[MAXN],ans[MAXN];
bool vis[MAXN];
int fa[MAXN],dep[MAXN];
void dijkstra(){
	memset(dis,0x3f,sizeof(ll)*(n+5));
	priority_queue<pii>q;
	q.emplace(dis[1]=0,dep[1]=1);
	while(!q.empty()){
		int u=q.top().second;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].to)
			if(dis[e[i].v]>dis[u]+e[i].w){
				dis[e[i].v]=dis[u]+e[i].w;
				q.emplace(-dis[e[i].v],e[i].v);
				fa[e[i].v]=u;
				dep[e[i].v]=dep[u]+1;
			}
	}
}
void work(int x,int y,ll w){
	while(x^y){
		if(dep[x]<dep[y])swap(x,y);
		ans[x]=min(ans[x],w-dis[x]);
		x=fa[x];
	}
}

int main(){
	freopen("path.in","r",stdin);
	freopen("path.out","w",stdout);
	n=read(),m=read();
	for(int i=1,u,v,w;i<=m;++i){
		u=read(),v=read(),w=read();
		addedge(u,v,w),addedge(v,u,w);
	}
	dijkstra();
	memset(ans,0x3f,sizeof(ll)*(n+5));
	for(int i=1;i<=n;++i)
		for(int j=head[i];j;j=e[j].to)
			if(dis[e[j].v]!=dis[i]+e[j].w&&dis[i]!=dis[e[j].v]+e[j].w)
				work(i,e[j].v,dis[i]+dis[e[j].v]+e[j].w);
	for(int i=2;i<=n;++i)write(ans[i]>=INF?-1:ans[i]);
	return fw,0;
}

T3 列表

两个引理最为关键:

引理 1 一个列表子集 Sa 最后能被得到当且仅当 |S|=N+1,且对于任意 i[0,N],列表初始中间一段下标 [N+1i,N+1+i]2i+1 个数中至少包含 i+1S 内的数。

引理 2 一个列表子集 AS 最后能被得到当且仅当对于任意 i[0,N],不在列表初始中间一段下标 [N+1i,N+1+i] 的数中至多包含 NiA 内的数。

然后原题转化为枚举连续数字段然后判定是否合法,一个 O(N3) 的东西就很容易能搓出来了(题解说能 O(N2) 算,我也不知道咋整的)。进一步,我们发现一个连续数字段的所有子集一定合法,所以每个左端点对应的最大合法右端点一定是单调的。故双指针枚举端点,线段树维护区间合法性可以做到 O(NlogN)

#include<bits/stdc++.h>
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
using namespace std;

char buf[1<<20],*p1=buf,*p2=buf;
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
constexpr int MAXN=4e5+5;
int n,a[MAXN],ans,pos[MAXN];
int max(int a,int b){
	return a>b?a:b;
}
int abs(int x){
	return x>0?x:-x;
}
#define lp p<<1
#define rp p<<1|1
#define mid ((s+t)>>1)
struct SegTree{
	int c,lazy;
}st[MAXN<<1];
void pushup(int p){
	st[p].c=max(st[lp].c,st[rp].c);
}
void pushlazy(int p,int x){
	st[p].c+=x,st[p].lazy+=x;
}
void pushdown(int p){
	if(!st[p].lazy)return;
	pushlazy(lp,st[p].lazy);
	pushlazy(rp,st[p].lazy);
	st[p].lazy=0;
}
void build(int s,int t,int p){
	if(s==t)return st[p].c=s,void();
	build(s,mid,lp),build(mid+1,t,rp);
	pushup(p);
}
void mdf(int l,int r,int k,int s=0,int t=n,int p=1){
	if(l<=s&&t<=r)return pushlazy(p,k);
	pushdown(p);
	if(l<=mid)mdf(l,r,k,s,mid,lp);
	if(mid<r)mdf(l,r,k,mid+1,t,rp);
	pushup(p);
}

int main(){
	freopen("list.in","r",stdin);
	freopen("list.out","w",stdout);
	n=read();
	for(int i=1;i<=(n<<1|1);++i)pos[a[i]=read()]=i;
	build(0,n,1);
	for(int l=1,r=0;l<=(n<<1|1);++l){
		while(st[1].c<=n){
			ans=max(ans,r++-l+1);
			pushlazy(1,1);
			mdf(abs(pos[r]-n-1),n,-1);
		}
		pushlazy(1,-1);
		mdf(abs(pos[l]-n-1),n,1);
	}
	return printf("%d\n",ans),0;
}

T4 种植

两遍 BFS 求出起点不可达或者终点不可达的点,这些点的状态是不影响答案的,最后答案乘上 2 的这类点数次方即可。然后把障碍物和不可达点的连通块缩点,注意斜线相邻的也是一个连通块

然后每个可达的点向它右上相邻的可达点或不可达缩的点连边。

最后拓扑排序求出从左下角到右上角的路径数。

#include<bits/stdc++.h>
using namespace std;

using ll=long long;
using pii=pair<int,int>;
constexpr int MAXN=1005,MOD=1e9+7,M=MAXN*MAXN;
constexpr int dx[8]={1,0,0,-1,1,-1,1,-1},dy[8]={0,1,-1,0,-1,1,1,-1};
int n,m,cant,f[MAXN*MAXN],ans[MAXN*MAXN];
bool vis[MAXN][MAXN],vis2[MAXN][MAXN],vs[MAXN][MAXN];
void add(int&x,int y){
	x=x+y>=MOD?x+y-MOD:x+y;
}
int id(int x,int y){
	return (x-1)*m+y;
}
int find(int x){
	if(f[x]^x)f[x]=find(f[x]);
	return f[x];
}
string a[MAXN];
ll power(ll a,int b){
	ll res=1;
	for(;b;a=a*a%MOD,b>>=1)if(b&1)res=res*a%MOD;
	return res;
}
set<int>pre[MAXN*MAXN],nxt[MAXN*MAXN];
int head[MAXN*MAXN],tot;
struct{int v,to;}e[MAXN*MAXN*3];
void addedge(int u,int v){
	e[++tot]={v,head[u]};
	head[u]=tot;
}
bool viss[MAXN*MAXN],vist[MAXN*MAXN];
int deg[MAXN*MAXN];
void bfs(){
	queue<pii>q;
	q.emplace(1,1);
	vs[1][1]=1;
	while(!q.empty()){
		int x=q.front().first,y=q.front().second;
		q.pop();
		vis[x][y]=1;
		if(x<n&&!vs[x+1][y]&&a[x+1][y]^'#')vs[x+1][y]=1,q.emplace(x+1,y);
		if(y<m&&!vs[x][y+1]&&a[x][y+1]^'#')vs[x][y+1]=1,q.emplace(x,y+1);
	}
	memset(vs,0,sizeof(vs));
	q.emplace(n,m);
	vs[n][m]=1;
	while(!q.empty()){
		int x=q.front().first,y=q.front().second;
		q.pop();
		vis2[x][y]=1;
		if(x>1&&!vs[x-1][y]&&a[x-1][y]^'#')vs[x-1][y]=1,q.emplace(x-1,y);
		if(y>1&&!vs[x][y-1]&&a[x][y-1]^'#')vs[x][y-1]=1,q.emplace(x,y-1);
	}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			if((!vis[i][j]||!vis2[i][j])&&a[i][j]^'#')
				++cant;
}
void toposort(){
	queue<int>q;
	for(int i=1;i<=id(n,m)+2;++i)if(!deg[i])ans[i]=1,q.emplace(i);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].to){
			add(ans[e[i].v],ans[u]);
			if(--deg[e[i].v])continue;
			q.emplace(e[i].v);
		}
	}
}

signed main(){
	freopen("plant.in","r",stdin);
	freopen("plant.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		a[i]=' '+a[i];
	}
	bfs();
	iota(f+1,f+n*m+1,1);
	// 合并非法点
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			if(vis[i][j]&&vis2[i][j])continue;
			for(int k=0,fx,fy;k<8;++k){
				fx=i+dx[k],fy=j+dy[k];
				if(fx<1||fx>n||fy<1||fy>m||(vis[fx][fy]&&vis2[fx][fy]))continue;
				f[find(id(fx,fy))]=find(id(i,j));
			}
		}
	// 连边
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			if(vis[i][j]&&vis2[i][j])continue;
			for(int k=0,fx,fy;k<6;k+=2){
				fx=i+dx[k],fy=j+dy[k];
				if(fx>n||fy<1)pre[find(id(i,j))].emplace(id(n,m)+1);
				else if(fx<1||fy>m||!vis[fx][fy]||!vis2[fx][fy])continue;
				else pre[find(id(i,j))].emplace(id(fx,fy));
			}
			for(int k=1,fx,fy;k<6;k+=2){
				fx=i+dx[k],fy=j+dy[k];
				if(fx<1||fy>m)nxt[find(id(i,j))].emplace(id(n,m)+2);
				else if(fx>n||fy<1||!vis[fx][fy]||!vis2[fx][fy])continue;
				else nxt[find(id(i,j))].emplace(id(fx,fy));
			}
		}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			if((vis[i][j]&&vis2[i][j])||find(id(i,j))^id(i,j))continue;
			for(auto s:pre[id(i,j)])addedge(s,id(i,j)),viss[s]=1,++deg[id(i,j)];
			for(auto t:nxt[id(i,j)])addedge(id(i,j),t),vist[t]=1,++deg[t];
		}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			if(!vis[i][j]||!vis2[i][j])continue;
			if(!viss[id(i,j)]){
				int fx=i-1,fy=j+1;
				if(fx<1||fy>m)addedge(id(i,j),id(n,m)+2),++deg[id(n,m)+2];
			}
			if(!vist[id(i,j)]){
				int fx=i+1,fy=j-1;
				if(fy<1||fx>n)addedge(id(n,m)+1,id(i,j)),++deg[id(i,j)];
				else addedge(id(fx,fy),id(i,j)),++deg[id(i,j)];
			}
		}
	// 拓扑排序求方案数 
	toposort();
	cout<<(ll)ans[id(n,m)+2]*power(2,cant)%MOD<<'\n';
	return 0;
}
posted @   Laoshan_PLUS  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示