51Nod 试题泛做1
A-排船的问题
很显然,这个数据范围用二分来找这个最长的最短是OK的,
然后我们就判断一下二分到的东西,用一个贪心,就是尽可能将每一个往左边放,但不能与船重叠,也不能超过我们二分到的最长的绳的长度,因为要尽可能给后面留出空间,让后面的绳的长度不超过我们二分到的长度。
然后如果最后极限的方法仍飞出了港口,那自然就是不行的,就继续二分。
可以这样理解。
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=5e4+5; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int n,x,m; int p[MAXN]; inline bool check(int pos) { int head=0,tail=0; for(int i=1;i<=n;i++) { head=tail; if(head+x>p[i]+pos) return false; tail=max(head+2*x,p[i]-pos+x); } if(tail>m) return false; return true; } int main() { n=read(),x=read(),m=read(); for(int i=1;i<=n;i++) p[i]=read(); if((x*2*n)>m) {printf("-1\n");return 0;} int l=0,r=m-1,ans; while(l<=r) { int mid=(l+r)>>1; if(check(mid))ans=mid,r=mid-1; else l=mid+1; } printf("%d\n",ans); return 0; }
B-稳定桌
首先,我们看复杂度,题目数据范围是 ,那么一种 会是比较可能的。
然后分析题目可以想到一个方法:从小到大枚举我们要留下的最长桌腿的长度 ,将长度大于 的桌腿全删掉,此时考虑一下是否删够了,即我们枚举到的 的数量是否超过了剩下数量的一半,超过就直接更新答案好了,没满足就还得从比 小的里面删。
然后就来到了本题的关键部分:
该如何筛选比 小的桌腿呢?
显然,若假设还需删 条,那么肯定是在小的里面找消耗能量前 小的删掉。教练第一反应是平衡树,但太难写也太难调,所以我们这里考虑到用权值线段树。
取所有代价中的最大值 ,以 为值域建立权值线段树,在此过程中我们要记录两个量, 是子树中所有代价之和, 是子树中桌腿的数量。
然后我们要保证有序,就在枚举的同时,将每一次枚举完的桌腿都插到树里,将小的往左插,大的往右插。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=1e5+5; const int INF=0x7f7f7f7f7f7f7f7f; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int n; vector<int>V[MAXN]; int t[MAXN<<2],num[MAXN<<2]; inline void pushup(int p) { t[p]=t[p<<1]+t[p<<1|1]; num[p]=num[p<<1]+num[p<<1|1]; return; } inline void change(int p,int l,int r,int k) { if(l==r) { num[p]++,t[p]+=k; return; } int mid=(l+r)>>1; if(k<=mid) change(p<<1,l,mid,k); else change(p<<1|1,mid+1,r,k); pushup(p); } inline int ask(int p,int l,int r,int k) { if(l==r) return l*k; int mid=(l+r)>>1; if(num[p<<1]==k) return t[p<<1]; else if(num[p<<1]>k) return ask(p<<1,l,mid,k); else return t[p<<1]+ask(p<<1|1,mid+1,r,k-num[p<<1]); } int l[MAXN],d[MAXN]; signed main() { n=read();int maxl=-1,maxd=-1;int ans=INF,sum=0,cnt=0,add; for(int i=1;i<=n;i++) l[i]=read(),maxl=max(maxl,l[i]); for(int i=1;i<=n;i++) { d[i]=read();maxd=max(maxd,d[i]); V[l[i]].push_back(d[i]);sum+=d[i]; } for(int i=1;i<=maxl;i++) if(V[i].size()) { cnt+=V[i].size(); for(int j=0;j<V[i].size();j++) sum-=V[i][j]; if(cnt>V[i].size()) add=ask(1,1,maxd,cnt-V[i].size()*2+1); else add=0; ans=min(ans,sum+add); for(int j=0;j<V[i].size();j++) change(1,1,maxd,V[i][j]); } printf("%lld\n",ans); return 0; }
C-金牌赛事
这是一道线段树优化DP。
设 表示前 条道路修若干条可得到的最大利润,那么我们枚举一个 ,则 从两种情况转移:
1.若第 条不修,则 ;
2.若第 条要修,则 ;
然而显然现在 是不够的,且 的处理也很麻烦,耗费时间和空间巨量。
所以我们来观察一下,第二种情况的转移是从一段区间里选最大值,那么我们可以用线段树优化成 。
同时,对于在线段树上算贡献也更好操作:
1.将每一个赛道右端点对应的左端点与贡献存在 vector 里面;
2.在往右扫到第 个时,在线段树上将区间 都剪掉花费 ;
3.在往右扫到第 个时,将右端点为 的赛道挑出来,其左端点为 ,贡献为 ,在线段树上将区间 加上贡献 ;
4.在每次枚举结束后,将 加到线段树上对应的位置 上。
区间修改,区间查询,时间复杂度 。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=2e5+5; const int INF=0x7f7f7f7f7f7f7f7f; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } struct Node { int l,p; }; int t[MAXN<<2],dat[MAXN<<2]; inline void pushup(int p) { t[p]=max(t[p<<1],t[p<<1|1]); return; } inline void addtag(int p,int l,int r,int k) { t[p]+=k,dat[p]+=k; return; } inline void pushdown(int p,int l,int r) { if(dat[p]) { int mid=(l+r)>>1; addtag(p<<1,l,mid,dat[p]); addtag(p<<1|1,mid+1,r,dat[p]); dat[p]=0; } return; } inline void change(int p,int l,int r,int a,int b,int k) { if(l>=a && r<=b) { addtag(p,l,r,k); return; } int mid=(l+r)>>1; pushdown(p,l,r); if(a<=mid) change(p<<1,l,mid,a,b,k); if(b>mid) change(p<<1|1,mid+1,r,a,b,k); pushup(p); return; } inline int ask(int p,int l,int r,int a,int b) { if(l>=a && r<=b) return t[p]; int mid=(l+r)>>1,ans=-INF; if(a<=mid) ans=max(ans,ask(p<<1,l,mid,a,b)); if(b>mid) ans=max(ans,ask(p<<1|1,mid+1,r,a,b)); return ans; } int dp[MAXN],c[MAXN]; int n,m; vector<Node>V[MAXN]; signed main() { n=read(),m=read(); for(int i=1;i<=n;i++) c[i]=read(); for(int i=1;i<=m;i++) { int L=read(),R=read(),P=read(); V[R].push_back({L,P}); } for(int i=1;i<=n;i++) { change(1,0,n,0,i-1,-c[i]); for(int j=0;j<V[i].size();j++) { int l=V[i][j].l,p=V[i][j].p; change(1,0,n,0,l-1,p); } dp[i]=max(dp[i-1],ask(1,0,n,0,i-1)); change(1,0,n,i,i,dp[i]); } printf("%lld\n",dp[n]); return 0; }
D-公园晨跑
首先看到环,就得断环成链。
要求最大值,考虑分 $$两部分分别最大化
距离可以前缀和预处理出来,那么就变成 ,为了好处理将相同位置的值放一起然后加括号 。
那么就变成了两个 RMQ 问题,没有修改,用线段树维护可行。
但是有一个重要的点,就是有可能出现查到的最大与最小的位置是同一个,这不符合要求,所以我们线段树维护的是下标而不是具体算出来的值,这方便最后处理时,若有重合,那么就往 左右两个区间去查,这里 是重合的那个下标。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=2e5+5; const int INF=0x7f7f7f7f7f7f7f7f; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int n,m; int d[MAXN],h[MAXN]; int maxn[MAXN<<2],minn[MAXN<<2]; int summax[MAXN],summin[MAXN]; inline int getmax(int x,int y) { return summax[x]>summax[y]?x:y; } inline int getmin(int x,int y) { return summin[x]<summin[y]?x:y; } inline void pushup(int p) { maxn[p]=getmax(maxn[p<<1],maxn[p<<1|1]); minn[p]=getmin(minn[p<<1],minn[p<<1|1]); return; } inline void build(int p,int l,int r) { if(l==r) { maxn[p]=minn[p]=l; return; } int mid=(l+r)>>1; build(p<<1,l,mid);build(p<<1|1,mid+1,r); pushup(p); return; } inline int askmax(int p,int l,int r,int a,int b) { if(l>=a && r<=b) return maxn[p]; int mid=(l+r)>>1,ans=0; if(a<=mid) ans=askmax(p<<1,l,mid,a,b); if(b>mid) ans=getmax(ans,askmax(p<<1|1,mid+1,r,a,b)); return ans; } inline int askmin(int p,int l,int r,int a,int b) { if(l>=a && r<=b) return minn[p]; int mid=(l+r)>>1,ans=0; if(a<=mid) ans=askmin(p<<1,l,mid,a,b); if(b>mid) ans=getmin(ans,askmin(p<<1|1,mid+1,r,a,b)); return ans; } inline int calcmax(int x,int y) { if(x>y) return 0; return askmax(1,1,n*2+1,x,y); } inline int calcmin(int x,int y) { if(x>y) return 0; return askmin(1,1,n*2+1,x,y); } inline int solve(int x,int y) { int maxi=calcmax(x,y),mini=calcmin(x,y); if(mini!=maxi) return summax[maxi]-summin[mini]; int maxx=getmax(calcmax(x,maxi-1),calcmax(maxi+1,y)); int minx=getmin(calcmin(x,mini-1),calcmin(mini+1,y)); return max(summax[maxi]-summin[minx],summax[maxx]-summin[mini]); } signed main() { n=read(),m=read(); for(int i=1;i<=n;i++) d[i+1]=d[i+n+1]=read(); for(int i=1;i<=n;i++) h[i]=h[i+n]=read(); int sum=0;summax[0]=-INF,summin[0]=INF; for(int i=1;i<=n*2;i++) sum+=d[i],summax[i]=sum+2*h[i],summin[i]=sum-2*h[i]; build(1,1,n*2+1); for(int i=1;i<=m;i++) { int l=read(),r=read(); if(l<=r) printf("%lld\n",solve(r+1,n+l-1)); else printf("%lld\n",solve(r+1,l-1)); } return 0; } /* sum[y]+2*h[y]取max sum[x]-2*h[x]取min */
E-幸运树
看到幸运边,考虑如何特殊处理。
可以将所有幸运边断开,形成若干个(假设是 )连通块 ,然后有一个显然的结论:两个不同连通块的点之间一定有幸运边。
因为没有要求方案,所以我们就直接用公式算:
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=1e5+5; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int n; struct edge { int to,nxt,len; }e[MAXN<<1]; int head[MAXN],cnt; inline void add(int x,int y,int z) { e[++cnt].to=y; e[cnt].len=z; e[cnt].nxt=head[x]; head[x]=cnt; return; } inline bool check(int x) { int xx=x; while(xx) { int pos=xx%10;xx/=10; if(pos!=4 && pos!=7) return false; } return true; } bool vis[MAXN]; int cntt; inline void dfs(int x) { vis[x]=true;cntt++; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to,z=e[i].len; if(vis[y] || z) continue; dfs(y); } return; } signed main() { n=read(); for(int i=1;i<=n-1;i++) { int x=read(),y=read(),z=read(); int w=check(z); add(x,y,w),add(y,x,w); } int ans=0; for(int i=1;i<=n;i++) if(!vis[i]) { cntt=0;dfs(i); ans+=cntt*(n-cntt)*(n-cntt-1); } printf("%lld\n",ans); return 0; }
F-犯罪计划
点击查看代码
本文作者:Code_AC
本文链接:https://www.cnblogs.com/code-ac/p/17497503.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步