一小时训练7

一小时训练7

这次一共有两道题,但做出了第一道。以下题解少数部分参考其他题解。

问题1:New Year Book Reading (CodeForces - 500C)

题目大意

现在你有一些书,它们可以被摞成一垛。每天你有一个阅读任务,只读一本书b[i]。每一本书有一个重量w[i]。你每一天需要从其中抽出你所需要的那一本书(将上面的书挪开,取出我们想要的书,再把已经挪开的书放回去,再把当前的书读完,放到整个书堆的顶部),我们定义每一次操作的代价为我们所挪开的书的重量的总和(不包含当前我们所需要的书)。现在的任务是求在一个初始的书的序列的情况下(没有给出),所有操作的代价的总和最小值。
书的数量:2<=n<=500
天数:1<=m<=1000
重量:1<=w[i]<=100
需要的书:1<=b[i]<=n
Sample Input
3 5
1 2 3
1 3 2 3 1
Sample Output
12

思路
这道题我们可以贪心(模拟)地想。首先,从前往后遍历整个需求数组,如果当前这个数从未出现过,那么它的最小代价为之前的所有书的重量的总和(因为之前的书读过之后都放到了顶部,也就是一定在当前这本书的上面,所以当前代价>=之前所有书的总和)。那么我们完全可以就让它等于这个最小值吧(这当然是合法的)。然后,如果我们访问到了一个之前存在过的书,那么在之前的那一次就已经把它放到了书堆的顶端,于是它这次所需要的代价即为在两次需求之间的不包括他们的所有书的重量的总和,那么这就是一个常值,与书的初始顺序无关。那么我们就可以根据第一次访问完所有的书的顺序来放书,然后跑模拟就可以了(可以用前缀和来实现)。
代码

#include<cstdio>
#include<cstring>
#define MAXN 500
#define MAXM 1000
int n,m;
bool vis[MAXN+5];
int w[MAXN+5],ord[MAXM+5],sum[MAXM+5][MAXM+5];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&ord[i]);
    }
    //这里用来预处理两个点之间的重量的和。(由于数据范围较小,虽然比较低效,但是也能过)
    for(int i=1;i<=m;i++)
        for(int j=i;j<=m;j++)
        {
            memset(vis,0,sizeof(vis));
            for(int k=i;k<=j;k++)
                if(vis[ord[k]]==false)
                {
                    vis[ord[k]]=true;
                    sum[i][j]+=w[ord[k]];
                }
        }
    int ans=0;
    for(int id=1;id<=n;id++)
    {
        //当当前的书是第一次出现时,直接视为它的上一次需求是在0的位置
        int lastpos=0;
        for(int i=1;i<=m;i++)
        {
            if(ord[i]==id)
            {
                ans+=sum[lastpos+1][i-1];
                lastpos=i;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
/*
3 5
1 2 3
1 3 2 3 1

*/

问题2:New Year Domino (CodeForces - 500E)

另一份题解链接:http://blog.csdn.net/qq_34454069/article/details/79415900
题目大意

现在我们有一个坐标轴,上面有n个多米若骨牌,我们给出每一个多米诺骨牌的位置p[i]以及它的高度l[i],而且认为如果一个多米诺骨牌倒下后,只要碰到了他之后的一个牌(p[i]+l[i]==pi”>j时也算);放倒之后可以放倒任何在他所及之处的所有骨牌。但是并不是放倒任意一个后,后面的牌都会被放倒。那么这是可以花费加长长度的代价来加长当前的牌,使得后面的牌可以继续被放倒下去。现在给出q个询问,要求如果我们推倒第l个骨牌,使得第r个骨牌也被放倒的最小代价。
数据范围:
n,q<=2*10^5
l,r<=n
p[i],len[i]<=10^9

Sample Inout
6
1 5
3 3
4 4
9 2
10 1
12 1
4
1 2
2 4
2 5
2 6
Sample Output
0
1
1
2

思路
由于这道题的骨牌可以放倒它之后的所有的在合法范围内的所有的骨牌,我们便不能直接简单地当做只让当前的骨牌仅仅放倒他之后的骨牌(当合法个数>=2时)。那么我们可以这样来想:
对于所有的询问,我们可以离线处理。按照所有的询问的l进行排序,然后让考虑的范围从右往左,只考虑枚举的左顶点以右的范围。因为一个骨牌可能会覆盖多个骨牌,我们便运用并查集将这些骨牌合起来,并把它们合成一坨。然后我们也可以直接将两坨或多坨合起来。这样在这个范围内,骨牌们便被分为了许多坨,并且从其中任意一坨到达后面的一坨都必须花费代价。如果需要花费代价,我们便把需要花费的代价储存在这一坨的代表元素——第一个上面(一个“数组”),然后最后的答案就是Sum(l,fa[r]-1)(如果l与r并没有在同一坨里),减一的原因是因为我们并不需要让之后的坨坨再倒下了,因为那是额外的花费;如果l,r在同一坨里,那么答案显然是0;
现在我们需要考虑如何向左移动一个骨牌(范围)。首先,我们可以用一个stack存下所有的坨坨的代表元素(也就是坨坨里面的第一个),然后我们再将当前的这个第id个骨牌与stack里面的第top个骨牌作比较,如果当前的已经合并好的,以第id个骨牌为代表元素的坨坨所能覆盖的最远的位置(需要维护一个),足以覆盖以第top个骨牌,那么我们就把第二个坨坨合并到第一个坨坨里面,并把第top个骨牌从stack里面pop出去,把“数组”里面的第二坨的所有的值都设为0(因为他们都可以从第id个骨牌无代价的放倒过来),并对farpos[id]更新为max(farpos[id],farpos[top]);如果不能覆盖,就退出循环,并把第id个坨坨的最远的覆盖位置与第一个无法0代价到达的坨坨的代表元素的距离求出来,加到“数组”的第id个位置上,退出加入操作。
这里的“数组”需要频繁的修改和询问区间和,于是我们可以用线段树或树状数组来维护。
代码

#include<cstdio>
#include<stack>
#include<algorithm>
#define MAXN 200000
using namespace std;
typedef long long LL;
stack<int> sta;
struct Query
{
    int l,r,id;
}q[MAXN+5];
int N,Q;
LL farpos[MAXN+5],len[MAXN+5],pos[MAXN+5],ans[MAXN+5];
int fa[MAXN+5];
LL tree[MAXN+5],dp[MAXN+5];
void Init()
{
    for(int i=1;i<=MAXN;i++)
        fa[i]=i;
}
int Lowbit(int i)
{
    return i&(-i);
}
bool cmp(Query a,Query b)
{
    if(a.l==b.l)
        return a.r<b.r;
    return a.l>b.l;
}
int FindSet(int x)
{
    if(fa[x]==x)
        return x;
    return fa[x]=FindSet(fa[x]);
}
void UniteSet(int x,int y)
{
    int fx=FindSet(x);
    int fy=FindSet(y);
    if(fx==fy)
        return;
    fa[fx]=fy;
}
bool SameSet(int x,int y)
{
    return FindSet(x)==FindSet(y);
}
void Add(int i,LL val)
{
    while(i<=N)
    {
        tree[i]+=val;
        i+=Lowbit(i);
    }
}
LL Sum(int i)
{
    LL ret=0;
    while(i>=1)
    {
        ret+=tree[i];
        i-=Lowbit(i);
    }
    return ret;
}
void GetBlock(int id)
{
    farpos[id]=1LL*pos[id]+1LL*len[id];
    while(sta.empty()==false)
    {
        int top=sta.top();
        if(pos[top]<=farpos[id])
        {
            sta.pop();
            UniteSet(top,id);
            farpos[id]=max(farpos[id],1LL*farpos[top]);
            Add(top,-dp[top]);
            dp[top]=0LL;
        }
        else
            break;
    }
    if(sta.empty()==false)
    {
        int top=sta.top();
        dp[id]=pos[top]-farpos[id];
        Add(id,dp[id]);
    }
    sta.push(id);
}
LL GetSum(int l,int r)
{
    return Sum(r)-Sum(l-1);
}
int main()
{
    Init();
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        scanf("%lld %lld",&pos[i],&len[i]);
    scanf("%d",&Q);
    for(int i=1;i<=Q;i++)
    {
        scanf("%d %d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    sort(q+1,q+1+Q,cmp);
    int nl=N+1;
    for(int i=1;i<=Q;i++)
    {
        int l=q[i].l;
        int r=q[i].r;
        int id=q[i].id;
        while(nl>l)
        {
            GetBlock(nl-1);
            nl--;
        }
        if(SameSet(l,r)==true)
            ans[id]=0;
        else
            ans[id]=GetSum(l,fa[r]-1);
    }
    for(int i=1;i<=Q;i++)
        printf("%lld\n",ans[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/G20202502/article/details/79418331