8月19日考试 题解(堆+二分答案+树状数组+反悔贪心)

今天没能做出来一道题,但是部分分给的比较良心。继续加油吧。

T1 climb

题目大意:给定一棵含有$n$个结点的树,每条边有边权,根节点为$1$。对于某些结点,可以直接到达深度小于等于它的结点,花费为$(dep[x]-dep[to])*k$。问每个结点到根节点的最小代价。

考试的时候就差最后一步,没能把$O(n)$优化成$O(\log n)$,挺可惜的。

我们先遍历整棵树,把每个结点的深度和到根节点的距离求出来,分别记为$dep$和$dis$。

然后我们按照深度遍历结点,对于一个结点,它的答案有两种情况:

1.$dis[fa]+edge[i]$

2.找到深度小于等于它的且最小的$dis$,此时有$dis[to]+(dep[i]-dep[to])*k$。

对于第二种情况,我们自然可以用堆来维护这个最小的$dis$。时间复杂度$O(n\log n)$。

实际上我们可以先将边权减去$k$,这样在过程种可以省很多事,最后输出的时候在加上$dep*k$即可。(不知道为什么我的代码不这样操作会出锅……

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=500005;
const int inf=1e9;
int n,k,dep[maxn],dis[maxn],edge[maxn],fa[maxn],maxx;
bool ban[maxn];
struct node
{
    int pos,dis;
    bool operator < (const node &x) const{return x.dis<dis;}
};priority_queue<node> q;
vector<int> v[maxn];
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*10+ch-'0';ch=getchar();}
    return x*f;
}
int main()
{
    n=read();k=read();
    dep[1]=0;dis[1]=0;
    for (int i=2;i<=n;i++)
    {
        int f=read(),w=read();ban[i]=read();w-=k;
        fa[i]=f;dep[i]=dep[f]+1;dis[i]=dis[f]+w;edge[i]=w;
        v[dep[i]].push_back(i);maxx=max(maxx,dep[i]);
    }
    q.push((node){1,dis[1]});
    for (int i=1;i<=maxx;i++)
    {
        int minn=inf,x=q.top().dis;
        for (int j=0;j<v[i].size();j++)
        {
            int now=v[i][j];
            dis[now]=min(dis[now],edge[now]+dis[fa[now]]);
            if (!ban[now]) dis[now]=min(dis[now],x);
            minn=min(minn,dis[now]);
        }
        for (int j=0;j<v[i].size();j++)
        {
            if (!ban[v[i][j]]) dis[v[i][j]]=min(dis[v[i][j]],minn);
            q.push((node){v[i][j],dis[v[i][j]]}); 
        }
    }
    for (int i=1;i<=n;i++) printf("%d\n",dis[i]+dep[i]*k);
    return 0;
}

T2 h

题目大意:数轴上有$n$个点,它们的初始位置为$p$,移动速度为$v$。一些点可能在移动过程中相遇,如果可能相遇,那么这些时刻显然有一个是最小的。现在你可以删去$k$个点,然后求出最小的相遇时刻。如果不可能相遇输出"Forever"。

考试的时候没想到二分答案,纯粹是靠着良心的部分分骗了60分?(雾

我们先把点按$p$为关键字进行排序。将$t$时刻时它们的位置存入一个新数组中,记为$b$。开始时对于两个位置$i<j$,均有$a_i<a_j$;如果$b_i>b_j$,那么它们将会碰撞。

为了避免这种情况,我们要在$b$数组中把$i<j$且$b_i>b_j$的情况删去。也就是说我们要尽可能少地让$b$成为最长上升子序列。

于是我们可以用树状数组求最长上升子序列。时间复杂度$O(n\log^2 n)$。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const double eps=1E-8;
const double inf=1E12;
const int maxn=50005;
int tree[maxn],n,k;
struct node
{
    double p,v;
    int id;
    bool operator < (const node &x) const{return p<x.p;}
}a[maxn],b[maxn];
inline int lowbit(int x){return x&(-x);}
bool cmp(node x,node y){return x.p<y.p;}
inline void add(int x,int y)
{
    while(x<=n)
    {
        tree[x]=max(tree[x],y);
        x+=lowbit(x);
    }
}
inline int query(int x)
{
    int res=0;
    while(x>0)
    {
        res=max(res,tree[x]);
        x-=lowbit(x);
    }
    return res;
}
inline bool check(double t)
{
    memset(tree,0,sizeof(tree));
    for (int i=1;i<=n;i++) b[i].id=i,b[i].p=a[i].p+a[i].v*t;
    sort(b+1,b+n+1);
    for (int i=1;i<=n;i++) a[b[i].id].id=i;
    for (int i=1;i<=n;i++)
    {
        int x=a[i].id;
        int y=query(x-1);
        add(x,y+1);
    }
    return n-query(n)<=k;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin>>n>>k; 
    for (int i=1;i<=n;i++) cin>>a[i].p>>a[i].v;
    sort(a+1,a+n+1);
    double l=0,r=inf,mid;
    while(abs(l-r)>eps)
    {
        mid=(l+r)/2;
        if (check(mid)) l=mid;
        else r=mid;
    }
    if (abs(mid-inf)<=eps) cout<<"Forever";
    else cout<<fixed<<setprecision(4)<<mid;
    return 0;
}

 T3 pancake

题目大意:给定一个长度为$n$的序列,有$q$次询问。你可以将每个序列划分成$b_{1,1},b_{1,2},\cdots ,b_{1,k_i}$,但要求$\sum\limits_{i=1}^n k_i=k$且$\sum\limits_{i=1}^n \sum\limits_{j=1}^{k_i}  b_{i,j}$最小。每次询问是以下三种操作之一:1.$k+=1$ 2.$k-=1$ 3.$a[++n]=x$。对于一开始的序列和每一次询问,都需要输出这个最小值。

暴力乱搞混了25分。正解实在很妙,确实没想到。

可以发现,一个数划分的越平均越好。可以证明:

假设$a\geq b$

$(a^2+b^2)-(a+1)^2-(b-1)^2=2(b-a)-2<0$

所以划分的数的差值不超过$1$。

注意到对于同一个长度$len$,如果切的刀数越多,那么代价就越小,并且代价减小的速度是越来越慢的。令$cost(len,k)$表示长度为$len$的数切$k$刀的代价,那么$cost(len,k+1)-cost(len,k)$是单调上升且小于$0$的。

这样我们可以维护两个multiset,一个用来插入,一个用来删除。每次找到$cost(len,k+1)-cost(len,k)$的最小的那个值,加上它,然后删除;再在用来插入的set插入$cost(len,k+2)-cost(len,k+1)$,在用来删除的set插入$cost(len,k)-cost(len,k+1)$。删除操作也是一样的。这样我们就可以在$\log n$时间完成操作1和操作2。

对于操作3,我们其实可以暴力改。把上述的差分想象成一个表格,就相当于我们让别的地方少占一个格子,然后看新加入的数的这一列能多添加几个格子,直到答案最优为止。可以证明总操作数是$n\log n$级别的。

具体实现可以看代码。时间复杂度$O(n\log^2 n)$。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1000005;
const int inf=8e18;
int k[maxn],a[maxn],ans,n,q,cut,opt;//k:number
struct node
{
    int num,w;
    node(int a=0,int b=0):num(a),w(b){}
    bool operator < (const node &x) const{return w==x.w?num<x.num:w<x.w;}
};
multiset<node> s0,s1;//0:add;1:del
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*10+ch-'0';ch=getchar();}
    return x*f;
}
inline int get(int x,int k)
{
    if (x<k||k==0) return inf;
    int base=x/k;
    return base*base*(k-x%k)+(base+1)*(base+1)*(x%k);
}
inline void add()
{
    multiset<node>::iterator p=s0.begin();
    node A=*p;
    ans+=A.w;
    int id=A.num;
    s1.erase(s1.find(node(id,get(a[id],k[id]-1)-get(a[id],k[id]))));
    s0.erase(p);
    k[id]++;
    s0.insert(node(id,get(a[id],k[id]+1)-get(a[id],k[id])));
    s1.insert(node(id,get(a[id],k[id]-1)-get(a[id],k[id]))); 
}
inline void del()
{
    multiset<node>::iterator p=s1.begin();
    node A=*p;
    ans+=A.w;
    int id=A.num;
    s0.erase(s0.find(node(id,get(a[id],k[id]+1)-get(a[id],k[id]))));
    s1.erase(p);
    k[id]--;
    s0.insert(node(id,get(a[id],k[id]+1)-get(a[id],k[id])));
    s1.insert(node(id,get(a[id],k[id]-1)-get(a[id],k[id])));
}
signed main()
{
    n=read();q=read();cut=read();
    for (int i=1;i<=n;i++)
    {
        a[i]=read();
        ans+=a[i]*a[i];
        k[i]=1;
        s0.insert(node(i,get(a[i],2)-get(a[i],1)));
        s1.insert(node(i,get(a[i],0)-get(a[i],1)));
    }
    for (int i=1;i<=cut;i++) add();
    printf("%lld\n",ans);
    while(q--)
    {
        opt=read();
        if (opt==1) add();
        if (opt==2) del(); 
        if (opt==3)
        {
            a[++n]=read();
            ans+=a[n]*a[n];
            k[n]=1;
            s0.insert(node(n,get(a[n],2)-get(a[n],1)));
            s1.insert(node(n,get(a[n],0)-get(a[n],1)));
            int last=ans;
            while(1)
            {
                del();
                add();
                if (ans==last) break;
                last=ans;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
    
}

 

posted @ 2020-08-19 17:20  我亦如此向往  阅读(225)  评论(0编辑  收藏  举报