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

首先,我们看复杂度,题目数据范围是 \(1\leqslant n \leqslant 10^5\),那么一种 \(\mathcal{O}(n\log n)\) 会是比较可能的。

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

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

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

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

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

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

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

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

区间修改,区间查询,时间复杂度 \(\mathcal{O}(n\log n)\)

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

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

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

距离可以前缀和预处理出来,那么就变成 \(sum_y-sum_x+2h_x+2h_y\),为了好处理将相同位置的值放一起然后加括号 \(sum_y+2h_y-(sum_x-2h_x)\)

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

但是有一个重要的点,就是有可能出现查到的最大与最小的位置是同一个,这不符合要求,所以我们线段树维护的是下标而不是具体算出来的值,这方便最后处理时,若有重合,那么就往 \([l,pos-1],[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\))连通块 \(t_1,t_2,\dots,t_m\),然后有一个显然的结论:两个不同连通块的点之间一定有幸运边

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

\(ans_i=\sum\limits_{i=1}^m|t_i|\binom{n-|t_i|}{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-犯罪计划

点击查看代码

posted @ 2023-06-22 09:44  Code_AC  阅读(19)  评论(2编辑  收藏  举报