不相信自己的人,连努力的价值都没有。|

Code_AC

园龄:3年粉丝:5关注:3

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-稳定桌

首先,我们看复杂度,题目数据范围是 1n105,那么一种 O(nlogn) 会是比较可能的。

然后分析题目可以想到一个方法:从小到大枚举我们要留下的最长桌腿的长度 i,将长度大于 i 的桌腿全删掉,此时考虑一下是否删够了,即我们枚举到的 i 的数量是否超过了剩下数量的一半,超过就直接更新答案好了,没满足就还得从比 i 小的里面删。

然后就来到了本题的关键部分:
该如何筛选比 i 小的桌腿呢?

显然,若假设还需删 k 条,那么肯定是在小的里面找消耗能量前 k 小的删掉。教练第一反应是平衡树,但太难写也太难调,所以我们这里考虑到用权值线段树。

取所有代价中的最大值 maxd,以 1maxd 为值域建立权值线段树,在此过程中我们要记录两个量,sumi 是子树中所有代价之和,numi 是子树中桌腿的数量。

然后我们要保证有序,就在枚举的同时,将每一次枚举完的桌腿都插到树里,将小的往左插,大的往右插。

点击查看代码
#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。

dpi 表示前 i 条道路修若干条可得到的最大利润,那么我们枚举一个 j,则 i 从两种情况转移:
1.若第 i 条不修,则 dpi=dpi1
2.若第 i 条要修,则 dpi=dpj+valj+1,icostj+1,i
然而显然现在 O(n2) 是不够的,且 val 的处理也很麻烦,耗费时间和空间巨量。

所以我们来观察一下,第二种情况的转移是从一段区间里选最大值,那么我们可以用线段树优化成 O(logn)
同时,对于在线段树上算贡献也更好操作:
1.将每一个赛道右端点对应的左端点与贡献存在 vector 里面;
2.在往右扫到第 i 个时,在线段树上将区间 [0,i1] 都剪掉花费 ci
3.在往右扫到第 i 个时,将右端点为 i 的赛道挑出来,其左端点为 l,贡献为 p,在线段树上将区间 [0,l1] 加上贡献 p
4.在每次枚举结束后,将 dpi 加到线段树上对应的位置 i 上。

区间修改,区间查询,时间复杂度 O(nlogn)

点击查看代码
#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-公园晨跑

首先看到环,就得断环成链。

要求最大值,考虑分 $$两部分分别最大化

距离可以前缀和预处理出来,那么就变成 sumysumx+2hx+2hy,为了好处理将相同位置的值放一起然后加括号 sumy+2hy(sumx2hx)

那么就变成了两个 RMQ 问题,没有修改,用线段树维护可行。

但是有一个重要的点,就是有可能出现查到的最大与最小的位置是同一个,这不符合要求,所以我们线段树维护的是下标而不是具体算出来的值,这方便最后处理时,若有重合,那么就往 [l,pos1],[pos+1,r] 左右两个区间去查,这里 pos 是重合的那个下标。

点击查看代码
#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-幸运树

看到幸运边,考虑如何特殊处理。

可以将所有幸运边断开,形成若干个(假设是 m)连通块 t1,t2,,tm,然后有一个显然的结论:两个不同连通块的点之间一定有幸运边

因为没有要求方案,所以我们就直接用公式算:

ansi=i=1m|ti|(n|ti|2)

点击查看代码
#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 中国大陆许可协议进行许可。

posted @   Code_AC  阅读(20)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起