CSP-S 模拟赛 44
CSP-S 模拟赛 44
rnk20,
小技巧:
- 做差是找规律的常见手法。如果一遍不行,就再做一遍。
- 题目中没有对应数据点的特殊性质没有必要去打,除非它的代码极其简单。
T1 岛屿
找规律题。
首先两种特殊情况的部分分是好找的,一种就是调和级数,另一种是变相的调和级数,然后 40pts 就拿到了。
正解是抽象的。易得初始存在的边形成一个完美匹配,且新加的边也必须形成一个完美匹配。所以最后图中每个点的度数一定都为
考虑连边过程,连边过程中形成的一定是若干条链和若干个环。只有链对后续有影响,然后按照题解说的搞一下就出来了。代码就是一个公式:
#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 掉最后一条最短路边,然后再跑一遍最短路,复杂度
正解前置芝士:最短路树。
构造最短路树,注意不用真的构造,只需保存节点深度和父亲。我们思考如果一个点到它父亲的边被 ban 了怎么办,结果是我们需要一条边连接该点子树内和子树外的任意一节点。
很难处理,那我们转换角度:对于任意一条非树边,它能给哪些点做贡献呢?
设当前节点为
其中
- 暴跳。理论复杂度
,但只是理论。 - 树剖维护,理论复杂度
。 - 并查集路径压缩维护。
#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 一个列表子集
引理 2 一个列表子集
然后原题转化为枚举连续数字段然后判定是否合法,一个
#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 求出起点不可达或者终点不可达的点,这些点的状态是不影响答案的,最后答案乘上
然后每个可达的点向它右上相邻的可达点或不可达缩的点连边。
最后拓扑排序求出从左下角到右上角的路径数。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?