JZOJ 8.7 B组总结

45 篇文章 0 订阅
37 篇文章 0 订阅

第一题

Description
  有n堆石子,从1~n编号,其石子总数为2^k。
  每次可以选择两堆石子a和b,满足a堆的石子数不比b堆的多,记c为a的石子数。然后可以进行以下操作:从b堆石子中拿c这么多的石子到a堆中。   
  要求你给出一个方案,使得最后有一堆石子的数目达到2^k。

Input
  第一行两个正整数n,k。   
  第二行n个非负数ai。
  
Output

  输出若干行,每行两个数a,b,表示每次操作中的两堆石子的编号,必须满足题中所给的大小关系!

Sample Input

2 2
3 1

Sample Output

2 1
1 2

Hint

【数据规模】  对于30%的数据,n=2;   
对于100%的数据,n<=100000,k<=31。
【注释】   此题需要Special Judge。


思路:暴力
要使得最终得到2^k
转成二进制就是只有第一个1,其余都为0
那么就可以利用这个性质,从后向前依次将所有数的最后一位变0(因为当枚举到前面,后面怎么加都是0)


代码:

#include<cstdio>
#include<iostream>
using namespace std;
int n,k,l;
long a[100000];
int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++) scanf("%ld",&a[i]);
    l=0;
    for (int i=1;i<=k;i++)
        for (int j=n;j>=1;j--)
            if ((a[j]&(1<<(i-1)))!=0)
                if (l==0) l=j;
                else
                {
                    if (a[j]>a[l])
                    {
                        a[j]-=a[l];
                        a[l]=a[l]<<1;
                        printf("%d %d\n",l,j);
                        l=0;
                    }
                    else 
                    {
                        a[l]-=a[j];
                        a[j]=a[j]<<1;
                        printf("%d %d\n",j,l);
                        l=0;
                    }
                }
    return 0;
}

第二题

Description

  编号为1~n的n个城市,每个城市有两个权值Ai和Bi。
  对于两个城市i和j,i可到j当且仅当j>i,而费用为(j-i)*Ai+Bj。
  求从城市1到城市n的最小费用。

Input

  第一行一个正整数n。
  第二行n个正整数,第i个表示Ai。
  第三行n个正整数,第i个表示Bi。

Output

  一个数,表示最小的费用。

Sample Input

4
2 9 5 4
9 1 2 2

Sample Output

8

Hint

【数据规模】
  对于20%的数据,1<=n<=100;
  对于50%的数据,1<=n<=3000;
  对于100%的数据,1<=n<=100000,1<=Ai,Bi<=10^9。


思路:
DP+队列
更新i的状态可以是1~i-1,但是有很多状态是没有用的(永远小),所以弄一个队列存下可能有用的状态
队列:若有两个点j和k(满足j< k),若要k比j优,那么k->i <= j->i,且a[k]<=a[j],那就可以把k扔掉。每次更新完f[i]就把它加入队尾,然后维护队列。
斜率优化+二分
首先,知道f[i]=min(f[j]+(j-i)*a[i]+b[j]),这应该每个人都知道
=min(f[j]+b[j]+j*a[i])-i*a[i]
发现,只有j*a[i]项存在两个不同的值(i和j)
那么类似于这种情况的都可以用斜率优化
设g[j]=f[j]+b[j]
那么原式=min(g[j]+j*a[i])-i*a[i]
运用方法①的思路,要求出最优的点,如果枚举两个位置j和k,让j,k< i,j< k
那么如果g[j]+j*a[i]< g[k]+k*a[i]
=(j-k)*a[i]< g[k]-g[j]
=-a[i]<(g[k]-g[j])/(k-j)
然后我们就可以构造出一个单调上升的凸多边形的下壳,那么现在就可以去二分求出一个最优的点,如果当前求出的斜率(g[k]-g[j])/(k-j)>=-a[i],就将r左移,不然就右移l
在求出一个最优点,再存起来就行了


方法①代码:

#include<cstdio>
#include<iostream>
using namespace std;
long long a[100000],b[100000],f[100000],k[100000];
int n,m,i,j,l,x;
long long much(int x,int y)
{
    return f[x]+a[x]*(y-x);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (int i=1;i<=n;i++) scanf("%lld",&b[i]);
    l=1;
    for (int i=2;i<=n-1;i++)
    {
        if (k[i]!=0&&a[k[i]]<a[l])
            if (much(l,i)>=much(k[i],i)) l=k[i];
            else 
            {
                x=(much(k[i],i)-much(l,i))/(a[l]-a[k[i]])+i+1;
                if (x<=n&&(k[x]==0||a[k[i]]<a[k[x]])) k[x]=k[i];
            }
        if (a[i]<a[l])
        {
            f[i]=much(l,i)+b[i];
            x=b[i]/(a[l]-a[i])+i+1;
            if (x<=n&&(k[x]==0||a[i]<a[k[x]])) k[x]=i;
        }
    }
    if (k[n]>0&&a[k[n]]<a[l]&&much(l,n)>much(k[n],n)) l=k[n];
    printf("%lld",much(l,n)+b[n]);
}

方法②代码:

#include<cstdio>
#include<iostream>
using namespace std;
int n,head,tail,mid,l,r,j;
long long a[100000],b[100000],f[100000],k[100000],g[100000];
float xl(int x,int y)
{
    return (g[x]-g[y])/(x-y);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (int i=1;i<=n;i++) scanf("%lld",&b[i]);
    head=1; tail=1;
    k[1]=n; f[n]=0; g[n]=f[n]+b[n];
    for (int i=n-1;i>=1;i--)
    {
        if (head<tail)
        {
            l=head;r=tail;
            while (l<r-1)
            {
                mid=(l+r)/2;
                if (xl(k[mid+1],k[mid])<=-a[i]) r=mid; else l=mid;
            }
            if (xl(k[r],k[l])<=-a[i]) j=k[l]; else j=k[r];
        }
        else j=k[head];
        f[i]=g[j]+j*a[i]-i*a[i];
        g[i]=f[i]+b[i];
        if (head<tail)
        {
            while (xl(i,k[tail])>xl(k[tail],k[tail-1]))
            {
                tail--;
                if (tail==head) break;
            }
        }
        tail++;
        k[tail]=i;
    }
    printf("%lld",f[1]);
}

第三题

Description

有一个外星人控制了你的大脑。一开始你处于原点(0,0)。外星人有一个由(R,U,D,L)组成的长度为M 的操作序列,分别代表(右,上,下,左)。
平面上有N 个关键点,每当外星人给出一个操作,你需要在这个方向上找到最近的一个关键点,并走到那个点上。保证输入数据合法。
这里写图片描述
上图为第三个样例的图示。

Input

第一行两个整数N,M。
接下来N 行,每行两个整数xi,yi,代表第i 个点的坐标。
接下来一行,一个长度为M 的字符串,代表操作序列。

Output

一行两个整数,代表最终你所处的位置。

Sample Input

输入1:
4 4
1 1
1 0
0 1
0 0
RULD

输入2:
7 5
0 0
0 1
0 -1
1 0
1 -1
3 0
3 -1
DRRUD

输入3:
10 6
0 0
1 1
2 1
0 2
-1 2
-1 3
2 3
2 4
4 3
2 -1
ULURDL

Sample Output

输出1:
0 0

输出2:
3 -1

输出3:
1 1

Data Constraint

56%的数据,N≤3000,M≤3000。
100%的数据,N,M≤100000,xi,yi≤200000。


思路:二分+排序
先将坐标排序,一个数组存横坐标小到大排,一个数组存纵坐标小到大排
将每一个寻找,用二分实现


代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct cf{int x,y;}a[100010],b[100010],tmp1,tmp2;
bool cmp1(cf a,cf b){if (a.y<b.y||(a.y==b.y&&a.x<b.x)) return true; else return false;}
bool cmp2(cf a,cf b){if (a.x<b.x||(a.x==b.x&&a.y<b.y)) return true; else return false;}
int lx,ly,n,m;
char c;
int efl(cf s)
{
    int l=1,r=n;
    while (l<r)
    {
        int mid=(l+r)>>1;
        if (b[mid].x<s.x||(b[mid].x==s.x&&b[mid].y<s.y)) l=mid+1; else r=mid;
    }
    return l;
}
int efh(cf s)
{
    int l=1,r=n;
    while (l<r)
    {
        int mid=(l+r)>>1;
        if (a[mid].y<s.y||(a[mid].y==s.y&&a[mid].x<s.x)) l=mid+1; else r=mid;
    }
    return l;
}
int main()
{
    freopen("tratincice.in","r",stdin);
    freopen("tratincice.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&lx,&ly);
        a[i].x=b[i].x=lx;
        a[i].y=b[i].y=ly;
    }
    sort(a+1,a+n+1,cmp1);
    sort(b+1,b+n+1,cmp2);
    for (int i=1;i<=m;i++)
    {
        while ((c=getchar())!='D'&&c!='R'&&c!='U'&&c!='L');
        if (c=='U')
        {
            int w=efl(tmp2)+1;
            tmp1.x=b[w].x;tmp1.y=b[w].y;
            tmp2.x=b[w].x;tmp2.y=b[w].y;
        }
        if (c=='D')
        {
            int w=efl(tmp2)-1;
            tmp1.x=b[w].x;tmp1.y=b[w].y;
            tmp2.x=b[w].x;tmp2.y=b[w].y;
        }
        if (c=='L')
        {
            int w=efh(tmp1)-1;
            tmp1.x=a[w].x;tmp1.y=a[w].y;
            tmp2.x=a[w].x;tmp2.y=a[w].y;
        }
        if (c=='R')
        {
            int w=efh(tmp1)+1;
            tmp1.x=a[w].x;tmp1.y=a[w].y;
            tmp2.x=a[w].x;tmp2.y=a[w].y;
        }
    }
    printf("%d %d",tmp1.x,tmp1.y);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值