noip模拟12
A 花鳥風月
对于每个区间,若左边端点为 ,右边端点为 ,那这个地方能放下的线段数则为 。
那么每进来一个坏点,只会影响它的前驱后继的区间。
那我们用 set 或者 map 维护一下前驱后继,每次加点去抵消它的区间的影响,再加回来就可以了。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; int n,a,m; unordered_map<int,int>mp; map<int,int>s; signed main() { // freopen("in.in","r",stdin); // freopen("ans.txt","w",stdout); freopen("A.in","r",stdin); freopen("A.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>a>>m; int lstans=(n+1)/(a+1); s[0]=1,s[n+1]++; int cnt=0; for(int i=1;i<=m;i++) { int now;cin>>now; if(s[now]) { cout<<lstans<<"\n"; continue; } int ans=lstans; int l=(--s.lower_bound(now))->first,r=(s.upper_bound(now))->first; ans-=(r-l)/(a+1); ans+=(now-l)/(a+1); ans+=(r-now)/(a+1); s[now]=1; cout<<ans<<"\n"; lstans=ans; } return 0; }
B 九莲宝灯
非常好,我又成唐氏了。
考虑朴素式子:
现在想一个问题,如果找一个点,到树上某三个点的距离和相等,那么这个距离一定等同于每两点距离之和除以二,即
这样就把 优化到 。
然后,我们发现,在运算中,每一对数,假设为 都被加了 次。那就可以把每个数对进行分别的贡献计算。
这样就是 。
然后考虑一条边的贡献,和之前一道题类似,等价于子树内的个数*字数外的个数,再乘以边权。
那直接一遍 dfs 就出来了。
要用 __int128
统计答案,然后顺便拿了最优解。
点击查看代码
#include<bits/stdc++.h> using namespace std; #define int long long using w=__int128; int n; const int N=2e5+5; struct node{ int to,next,w; }e[N<<1]; int head[N],cnt; inline void add(int u,int v,int w) { e[++cnt].to=v,e[cnt].next=head[u],e[cnt].w=w; head[u]=cnt; } bitset<N>a,b,c; inline int read() { register int s=0; register char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') s=(s<<1)+(s<<3)+(c^48),c=getchar(); return s; } w ansa,ansb,ansc; int ca[N],cb[N],cc[N]; int ka,kb,kc; void dfs(int u,int f) { if(a[u])ca[u]++; if(b[u])cb[u]++; if(c[u])cc[u]++; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(v==f) continue; dfs(v,u); ca[u]+=ca[v],cb[u]+=cb[v],cc[u]+=cc[v]; } for(int i=head[u];i;i=e[i].next) { int v=e[i].to; if(v==f) continue; ansc+=(w)e[i].w*(ka-ca[v])*cb[v]; ansa+=(w)e[i].w*(kb-cb[v])*cc[v]; ansb+=(w)e[i].w*(kc-cc[v])*ca[v]; ansc+=(w)e[i].w*(kb-cb[v])*ca[v]; ansa+=(w)e[i].w*(kc-cc[v])*cb[v]; ansb+=(w)e[i].w*(ka-ca[v])*cc[v]; } } void write(w x) { if(x>9) write(x/10); putchar(x%10+'0'); } signed main() { freopen("B.in","r",stdin); freopen("B.out","w",stdout); n=read(); for(int i=1;i<n;i++) { int u=read(),v=read(),w=read(); add(u,v,w),add(v,u,w); } ka=read(); for(int i=1;i<=ka;i++) a[read()]=1; kb=read(); for(int i=1;i<=kb;i++) b[read()]=1; kc=read(); for(int i=1;i<=kc;i++) c[read()]=1; dfs(1,0); ansa*=(w)ka,ansb*=(w)kb,ansc*=(w)kc; w ans=(w)ansa+ansb+ansc; ans>>=1; write(ans); return 0; }
C 石上三年
真是拿 solution 捏出来的题。。
因为他的 给的很小,所以解法一定和 有联系。
换句话说,部分分是依据 的递进给出的,那对正解一定又一定的提示性。
考虑一个很反常的结论:如果起点到终点的矩形中,存在不可走的点,那最短路径一定从某个不可走的点边上过去。
很显然的,如果最短路有多条,那最短路可以擦着不可走的点,也可以不走。那擦着走也就一定是最短路的一部分。
如果最短路只有一条,那一定是因为又障碍阻隔了,那贴着他走显然最优啊。
这样一来,就可以先预处理出每个障碍上下左右对于全局的最短路,然后每次询问枚举全部的障碍,把最短路分成两部分:起点到当前点+当前点到终点就好了。
预处理复杂度 ,询问是 。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,m,k,q; const int N=2e5+5; set<pair<int,int> >mp; int stx,sty,edx,edy; struct node{ int x,y,d,f; inline bool operator<(const node &ll) const{ return ll.f<f; } }; map<pair<int,int>,bool>vis; inline int man(int x,int y,int a,int b) { return abs(x-a)+abs(y-b); } const int dx[4]={0,0,1,-1}; const int dy[4]={1,-1,0,0}; namespace sub1{ void gowork() { while(q--) { cin>>stx>>sty>>edx>>edy; cout<<man(stx,sty,edx,edy)<<"\n"; } } } namespace sub2{ int x,y; void gowork() { while(q--) { cin>>stx>>sty>>edx>>edy; if(mp.count({stx,sty})||mp.count({edx,edy})) { cout<<"-1\n";continue; } int ans=man(stx,sty,edx,edy); if(n==1) { if(stx==edx&&x==stx&&y>min(sty,edy)&&y<max(sty,edy)) cout<<"-1\n"; else cout<<ans<<"\n"; } else if(m==1) { if(sty==edy&&y==sty&&x>min(stx,edx)&&x<max(stx,edx)) cout<<"-1\n"; else cout<<ans<<"\n"; } else { if(stx==edx&&x==stx&&y>min(sty,edy)&&y<max(sty,edy)) ans+=2; else if(sty==edy&&y==sty&&x>min(stx,edx)&&x<max(stx,edx)) ans+=2; cout<<ans<<"\n"; } } } } vector<pair<int,int> >b; int *dis[50][4][N]; void init() { for(int i=0;i<b.size();i++) { int x=b[i].first,y=b[i].second; for(int j=0;j<4;j++) { int nxtx=x+dx[j],nxty=y+dy[j]; if(nxtx<1||nxty<1||nxtx>n||nxty>m) continue; for(int k=1;k<=n;k++) { dis[i][j][k]=new int [m+4]; for(int o=1;o<=m;o++) dis[i][j][k][o]=1e9; } if(mp.count({nxtx,nxty})) continue; queue<node>q{}; dis[i][j][nxtx][nxty]=0; q.push({nxtx,nxty,0,0}); while(!q.empty()) { node now=q.front();q.pop(); for(int k=0;k<4;k++) { node nt=now; nt.x+=dx[k],nt.y+=dy[k]; if(nt.x<1||nt.y<1||nt.x>n||nt.y>m||mp.count({nt.x,nt.y})) continue; if(dis[i][j][nt.x][nt.y]<1e9) continue; dis[i][j][nt.x][nt.y]=now.d+1; nt.d++; q.push(nt); } } } } } inline bool ck(int a1,int b1,int a2,int b2) { if(a1>a2) swap(a1,a2); if(b1>b2) swap(b1,b2); for(int i=0;i<b.size();i++) { if(b[i].first>=a1&&b[i].first<=a2&&b[i].second>=b1&&b[i].second<=b2) return 1; } return 0; } inline int read() { register int s=0; register char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') s=(s<<1)+(s<<3)+(c^48),c=getchar(); return s; } signed main() { freopen("C.in","r",stdin); freopen("C.out","w",stdout); n=read(),m=read(),k=read(),q=read(); for(int i=1;i<=k;i++) { int x=read(),y=read(); mp.insert({x,y}); sub2::x=x,sub2::y=y; b.push_back({x,y}); } if(k==0) return sub1::gowork(),0; if(k==1) return sub2::gowork(),0; if(q==1) { while(q--) { stx=read(),sty=read(),edx=read(),edy=read(); if(mp.count({stx,sty})||mp.count({edx,edy})) { printf("-1\n");continue; } vis.clear(); priority_queue<node>q{}; q.push({stx,sty,0,man(stx,sty,edx,edy)}); bool tag=0; while(!q.empty()) { node now=q.top();q.pop(); if(vis[{now.x,now.y}]) continue; if(now.x==edx&&now.y==edy) { tag=1; printf("%d\n",now.d);break; } vis[{now.x,now.y}]=1; for(int i=0;i<4;i++) { node nxt=now; nxt.x+=dx[i],nxt.y+=dy[i]; if(nxt.x<1||nxt.x>n||nxt.y<1||nxt.y>m||mp.count({nxt.x,nxt.y})) continue; nxt.d++,nxt.f=nxt.d+man(nxt.x,nxt.y,edx,edy); q.push(nxt); } } if(!tag) printf("-1\n"); } return 0; } init(); // return clock(); while(q--) { stx=read(),sty=read(),edx=read(),edy=read(); if(mp.count({stx,sty})||mp.count({edx,edy})) { printf("-1\n");continue; } if(!ck(stx,sty,edx,edy)) { printf("%d\n",man(stx,sty,edx,edy));continue; } int ans=1e9; for(int i=0;i<b.size();i++) { for(int j=0;j<4;j++) { int x=b[i].first+dx[j],y=b[i].second+dy[j]; if(x<1||y<1||x>n||y>m) continue; ans=min(ans,dis[i][j][stx][sty]+dis[i][j][edx][edy]); } } printf("%d\n",(ans==1e9?-1:ans)); } return 0; }
D 東北新幹線
很牛逼的优化。没想到能这么写。
考虑朴素的状压 dp,容易设出 表示操作到 时、状态位 的最小花费。
那转移就很显然了,从上一位的某个状态,可以要这个 ,也可以不要这个 ,如果不要,就得加上花费 。
然后,对于修改操作,直接继承上次的状态,进行一轮 即可。而对于删除操作,则可以直接输出 的答案。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n; const int N=1e4+4; int rx,ry; struct node{ int x,y; }a[N]; int top; int dp[N][16384]; signed main() { freopen("D.in","r",stdin); freopen("D.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; int len=0; for(int i=1;i<(1<<14);i++) dp[0][i]=1e9; for(int i=1;i<=n;i++) { string c;cin>>c; if(c[0]=='A') { int x,y;cin>>x>>y; a[++top]={x,y}; for(int s=0;s<(1<<14);s++) { dp[top][s]=min(dp[top-1][s]+a[top].y,dp[top-1][s^a[top].x]); } } else { --top; } cout<<dp[top][0]<<"\n"; } }
时间复杂度 ,因为空间复杂度的原因,不能 AC。
这也是题目的最后几个大包开 的原因。
考虑怎么减省空间。
由上述知,每次加点会直接继承上一次的状态,每上一次则会向上回退一层。但是从一次删点往后的无数次操作都会以删的点的上个点为基础。
这就像一棵树一样。
对于一连串的加点,就像一条长长的树链,从上往下依次更新,并且每次更新的依据有且仅有他的父亲。而对于删点,就像跳父亲一样,向上跳很多后,从某个点开始另开一条树链去操作。
由于每个点的状态只与父亲有关,那么参照滚动数组的做法,可以直接继承父亲的 dp 数组指针。
那既然有分叉的情况,不能直接用一个指针顺流而下。对于一些特定树叉,可以直接用父亲的指针,其他的则必须另开新指针。
如何分配呢?轻重链剖分啊!
我们先对这棵操作树进行树剖,然后当我们计算到点 时,首先对于它的轻儿子,新开一个指针计算答案。然后去重儿子,让重儿子继承父亲的指针。并且让重儿子直接更新写个答案。这是因为一个点的重儿子最多只有一个,这样可以避免指针重复指向的问题。并且一条链上轻边的个数是不会超过 条的,那我们只需要开 大小的 dp 数组就可以了。
这样空间的问题就解决了,接下来要解决的大问题就是建树。
-
加点操作,会使头指针向下移动一位
-
删点操作,会使头指针向上移动一位
但是会出现森林的情况,比如先加一个点,再把它删掉。那么接下来的点就和原先没有关系了。那就需要一个虚拟原点去连所有的森林。
若删去一个点,那么接下来点接的地方就是被删点的父亲。这难以处理,因为会存在连续删除多次的情况,那我们可以用一个栈顶指针 来存储当前位的位置。
需要注意的是, 并不是元素值,它没有任何含义,所以需要一个 表示 时表示的元素。 会回退,则删除操作的答案可以直接依赖到 上,这就是它的父亲。
为了方便存储答案,还可以引入一个 来存储答案位置。当操作位加点时,,否则,。
这样就处理好了。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n; const int N=4e4+5; const int M=12; struct node{ int x,y,id; }a[N]; vector<int>e[N]; int fa[N],siz[N],son[N]; void dfs1(int u,int f) { fa[u]=f,siz[u]=1; for(int v:e[u]) { if(v==f) continue; dfs1(v,u),siz[u]+=siz[v]; if(siz[v]>siz[son[u]]) son[u]=v; } } int tim; void dfs2(int u,int t) { if(son[u]) dfs2(son[u],t); for(int v:e[u]) { if(v==fa[u]||v==son[u]) continue; dfs2(v,v); } } int ans[N]; int dp[M][16385]; int hp[16385]; void dfs(int u,int cnt) { // cout<<"now: "<<u<<"\n"; for(int v:e[u]) { if(v==fa[u]||v==son[u]) continue; // cout<<v<<" "; for(int s=0;s<16385;s++)dp[cnt+1][s]=2e9; for(int s=0;s<16385;s++) { dp[cnt+1][s]=min(dp[cnt][s]+a[v].y,dp[cnt][s^a[v].x]); // cout<<dp[cnt][s]<<" "; } // cout<<a[v].x<<" "<<a[v].y<<" "; ans[v]=dp[cnt+1][0]; // cout<<ans[v]<<"\n"; dfs(v,cnt+1); } if(son[u]) { // cout<<"son: "<<son[u]<<"\n"; for(int s=0;s<16385;s++) { hp[s]=dp[cnt][s],dp[cnt][s]=2e9; } for(int s=0;s<16385;s++) { dp[cnt][s]=min(hp[s]+a[son[u]].y,hp[s^a[son[u]].x]); } ans[son[u]]=dp[cnt][0]; dfs(son[u],cnt); } } int bel[N]; signed main() { freopen("D.in","r",stdin); freopen("D.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; int tp=1; bel[1]=1; a[tp].id=1; for(int i=1;i<=16384;i++) dp[1][i]=2e9; for(int i=2;i<=n+1;i++) { string c;cin>>c; if(c[0]=='A') { cin>>a[i].x>>a[i].y; a[i].id=i; e[a[bel[tp]].id].push_back(i); e[i].push_back(a[bel[tp]].id); bel[++tp]=i; } else --tp,a[i].id=a[bel[tp]].id; } dfs1(1,0),dfs2(1,1); dfs(1,1); for(int i=2;i<=n+1;i++) { // cout<<a[i].id<<" "; cout<<ans[a[i].id]<<"\n"; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!