2016"百度之星" - 初赛(Astar Round2B)解题报告

Preface

hdu5696~5701

1001 区间的价值

主要利用数据随机的特性。之前没接触过这类题,主要利用随机数据分布的特点,将一些 O(n2) 的时间复杂度用调和级数转化为 O(nlogn) 的时间复杂度。除此之外,还有一些特性,例如长度为 n 的最长上升序列的长度大约为logn n 个随机点的凸包点集大小大约为logn等等。。之后会收集下此类的题。回到这个题上,先说做法,递归处理区间 [l,r] 找出区间中的最小值和最大值,更新后,再去递归区间 [l,k1] 和区间 [k+1,r] 。其中 k 是区间[l,r]中最小值的下标。再利用 ans[i]=max(ans[i],ans[i+1]) 更新得到答案。显然这个做法是正确的,又由于数据是随机的,所以这个时间复杂度我们可以进行看成 O(nlogn) 的。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>

using namespace std;

typedef long long LL;
LL ans[110000];
LL a[110000];
void work(int l,int r)
{
    if (l>r) return ;
    if (l==r)
    {
        ans[1]=max(ans[1],a[l]*a[l]);
        return ;
    }
    int minn=l,maxx=l;
    for (int i=l;i<=r;i++)
    {
         if (a[i]<a[minn]) minn=i;
         if (a[i]>a[maxx]) maxx=i;
    }
    ans[r-l+1]=max(ans[r-l+1],a[minn]*a[maxx]);
    work(l,minn-1);
    work(minn+1,r);
}
int n;
int main()
{
    while (scanf("%d",&n)==1)
    {
        memset(ans,0,sizeof(ans));
        for (int i=1;i<=n;i++)
        {
            scanf("%I64d",&a[i]);
        }
        work(1,n);
        for (int i=n-1;i>=1;i--)
            ans[i]=max(ans[i],ans[i+1]);
        for (int i=1;i<=n;i++)
            printf("%I64d\n",ans[i]);
    }
    return 0;
}

1002 刷题计划

这里主要用到求解最大/最小乘积生成树的思想,具体做法之后会专门写一篇算法学习,先放上神犇的链接:
http://www.cnblogs.com/autsky-jadek/p/3959446.html
然后题目中要求要第一关键字之和至少要到 m ,我们只需要用背包搞一下就行了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>

using namespace std;

typedef long long LL;
const LL INF=1e18;
int n,m;
LL pa[500],pb[500],pc[500];
LL a[500],b[500],c[500];
LL w[500];
LL A[1100],B[1100];
struct point
{
    LL x,y;
}ans;
LL cross(point a,point b)
{
    return a.x*b.y-a.y*b.x;
}

int d[500];
LL sum;
LL f[1100];

int cmp(int a,int b)
{
    return pa[a]<pa[b];
}
point find(LL k1,LL k2)
{
    point num={0,0};
    for (int i=1;i<=n;i++)
        w[i]=-b[i]*k2+c[i]*k1;
    memset(A,0,sizeof(A));
    memset(B,0,sizeof(B));
    for (int i=0;i<=sum;i++)
        f[i]=-INF;
    f[0]=0;
    int pre=0;
    for (int i=1;i<=n;i++)
    {
        pre+=a[i];
        for (int j=pre,k1=j-a[i];j>=a[i];j--,k1--)
            if (f[k1]>-1e16&&f[k1]+w[i]>f[j])
            {
                f[j]=f[k1]+w[i];
                A[j]=A[k1]+b[i];
                B[j]=B[k1]+c[i];
            }
    }
    LL now=-INF;
    for (int i=m;i<=sum;i++)
        if (f[i]>now)
        {
            now=f[i];
            num=(point){A[i],B[i]};
        }
    if (num.x*num.y<ans.x*ans.y) ans=num;
    return num;
}   
void work(point k1,point k2)
{
    if (k1.x==k2.x&&k1.y==k2.y)
    {
        k2.x++,k2.y++;
    }
    int ka=k1.x-k2.x,kb=k1.y-k2.y;
    point now=find(ka,kb);
    point aa={k1.x-k2.x,k1.y-k2.y};
    point bb={now.x-k2.x,now.y-k2.y};
    if (cross(aa,bb)>0)
    {
        work(k1,now);
        work(now,k2);
    }
}
int main()
{
    while (scanf("%d%d",&n,&m)==2)
    {
        for (int i=1;i<=n;i++)
        {
            scanf("%I64d %I64d %I64d",&pa[i],&pb[i],&pc[i]);
            d[i]=i;
        }
        sort(d+1,d+1+n,cmp);
        for (int i=1;i<=n;i++)
            a[i]=pa[d[i]],b[i]=pb[d[i]],c[i]=pc[d[i]];
        ans.x=ans.y=2100000;
        sum=0;
        for (int i=1;i<=n;i++)
            sum+=a[i];
        work(find(1,0),find(0,-1));
        printf("%I64d\n",ans.x*ans.y);
    }
    return 0;
}

1003 瞬间移动

组合数学题。我们能够发现,当n固定时,整个答案为n-2阶等差数列,之后能推出公式

ans=Cn2n+m4

然后利用预处理出的逆元的前缀积,和阶乘的值 O(1) 搞一下就行了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>

using namespace std;

typedef long long LL;
const LL mod=1e9+7;
LL f[1100000],inv[1100000],ff[1100000];
LL C(int n,int m)
{
    if (n==0||m==0) return 1;
    LL res=(f[n]*inv[m])%mod;
    res=(res*inv[n-m])%mod;
    return res;
}
int n,m;
int main()
{
    f[0]=1;
    for (int i=1;i<=1000010;i++)
        f[i]=(f[i-1]*i)%mod;
    ff[1]=ff[0]=inv[1]=inv[0]=1;  
    for (int i=2;i<=1000010;i++)
    {
        inv[i]=(LL)(mod-mod/i)*inv[mod%i]%mod;
        ff[i]=inv[i];
    }
    for (int i=2;i<=1000010;i++)
        inv[i]=(inv[i-1]*inv[i])%mod;
    while (scanf("%d %d",&n,&m)==2)
    {
        printf("%I64d\n",C(n+m-4,n-2));
    }
    return 0;
}

1004 货物运输

首先我们能想到二分答案,然后问题在于如何判断一个答案是否存在方案。假设我们需要判断时间最多为 x 的方案是否存在。对于一个运输l[i] r[i] ,如果之间的距离小于 x ,我们就不用考虑了。如果大于x,我们要找出新建的站点两端 l r所在的最大的区间即可,这里我们只用讨论四种方案就行。最后看区间是否为空就可以判断是否存在方案了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>

using namespace std;

typedef long long LL;
int n,m;
int l[1100000],r[1100000];
int lx,rx,ly,ry;
void change(int x,int y)
{
    int l=x-y,r=x+y;
    lx=min(lx,l),ly=min(ly,r);
    rx=max(rx,l),ry=max(ry,r);
}
int check(int x)
{
    int x1=-1e9,x2=1e9,y1=-1e9,y2=1e9;
    for (int i=1;i<=m;i++)
    {
        if (r[i]-l[i]>x)
        {
            lx=ly=1e9,rx=ry=-1e9;
            change(l[i],r[i]-x);
            change(l[i],r[i]+x);
            change(l[i]-x,r[i]);
            change(l[i]+x,r[i]);
            x1=max(x1,lx),y1=max(y1,ly);
            x2=min(x2,rx),y2=min(y2,ry);
            if (x1>x2||y1>y2) return 0;
        }   
    }
    return 1;
}

int solve()
{
    int l=0,r=n-1;
    while (l<r)
    {
        int mid=(l+r)>>1;
        if (check(mid)) r=mid;
        else l=mid+1;
    }
    return l;
}
int main()
{
    while (scanf("%d %d",&n,&m)==2)
    {
        for (int i=1;i<=m;i++)
        {
            scanf("%d %d",&l[i],&r[i]);
            if (l[i]>r[i]) swap(l[i],r[i]);
        }
        int ans=solve();
        printf("%d\n",ans);
    }
    return 0;
}

1005 区间交

将线段的所有端点排序,对于同一位置的点,我们先考虑线段左端点的插入,再更新答案,最后考虑线段右端点的删除。那么如何更新答案呢。我们考虑以 i 为区间交的左端点的情况,由于所有数值为正,所以此时区间的右端点越大越好。如果此时插入的线段个数不少于k,那么后面一定有至少 k 个右端点,我们只要求出所有右端点中的第k大即可,那么这个是以 i 为左端点,线段右端点中第k大的位置为右端点的区间,更新下 ans 即可。插入和删除可以用线段树维护,不过我选择了模板功能强大的splay。插入时,将对应线段的右端点放进splay中,删除时,将对应线段右端点从splay中删除即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>

using namespace std;

typedef long long LL;
int l[110000],r[110000];
vector<int> p;
struct point 
{
    int id,x,type;
}a[310000];
LL v[110000];
int num;
int cmp(point a,point b)
{
    if (a.x!=b.x) return a.x<b.x;
    return a.type<b.type;
}
const int maxn=110000;
struct node
{
    int num,size,c[2],fa;
    LL data;
}tree[maxn];
int root=0,sum=0;
void update(int x)
{
    tree[x].size=tree[tree[x].c[0]].size+tree[tree[x].c[1]].size+tree[x].num;
}
void rotate(int x,int p)
{
    int y=tree[x].fa;
    if (tree[x].c[p]) tree[tree[x].c[p]].fa=y;
    tree[y].c[p^1]=tree[x].c[p];
    tree[x].c[p]=y;
    tree[x].fa=tree[y].fa;
    tree[y].fa=x;
    if (tree[x].fa)  if (y==tree[tree[x].fa].c[0]) tree[tree[x].fa].c[0]=x;
                                             else  tree[tree[x].fa].c[1]=x;
    update(y); 
    update(x);
}
void splay(int x)
{
    while (tree[x].fa!=0)
    {
        int y=tree[x].fa;
        int z=tree[y].fa;
        if (z==0)
        {
            if (tree[y].c[0]==x) rotate(x,1);
                             else rotate(x,0);
            break;
        }
        int a=y==tree[z].c[0]?1:0;
        int b=x==tree[y].c[0]?1:0;
        if (a^b)  rotate(x,b),rotate(x,a);
            else  rotate(y,a),rotate(x,b);
    }
    root=x;
}
void maintain(int x)
{
    while (x!=0)
        update(x),x=tree[x].fa;
}
void init(LL x,int sum)
{
     tree[sum].data=x;
     tree[sum].fa=0;
     tree[sum].c[0]=0;
     tree[sum].c[1]=0;
     tree[sum].num=1;
     tree[sum].size=1;
}
void insert(LL x,int sum)
{
    int y=root;
    int pos=0;
    init(x,sum);
    if (y==0)
    {
         root=sum;
         return;
    }
    while (true)
    {
        tree[y].size++;
        if (x==tree[y].data)
        {
            pos=y;
            tree[y].num++;
            maintain(y);
            break;
        }
        int p=1;
        if (x<tree[y].data) p=0;
        if (tree[y].c[p]==0)
        {
            tree[y].c[p]=sum;
            tree[sum].fa=y;
            break;
        }
        else y=tree[y].c[p];
    }
    if (pos==0) pos=sum;
    splay(pos);
}
int findpos(LL x,int z)
{
    int p,y=root,d=0;
    while (x!=tree[y].data)
    {
        p=0;
        if (x>tree[y].data) p=1;
        if (p&&z) d+=tree[tree[y].c[0]].size+tree[y].num;
        y=tree[y].c[p];
    }
    if (z) return d+tree[tree[y].c[0]].size+1;
      else return y;  
}
int find(int x,int p)
{
    x=findpos(x,0);
    splay(x);
    int y=root;
    y=tree[y].c[p];
    while (tree[y].c[p^1])
      y=tree[y].c[p^1];
    return y;
}
void del(LL x)
{
    int pos=findpos(x,0);
    splay(pos);
    if (tree[pos].num>1)
    {
        tree[pos].num--;
        tree[pos].size--;
    }
    else
    {
        if (tree[pos].c[0]!=0)
        {
            tree[tree[pos].c[0]].fa=0;
            int f=find(tree[pos].data,0);
            tree[tree[pos].c[1]].fa=f;
            tree[f].c[1]=tree[pos].c[1];
            maintain(tree[pos].c[1]);
            root=tree[pos].c[0];
            tree[pos].c[0]=0;
            tree[pos].c[1]=0;
        }
        else
        {
            tree[tree[pos].c[1]].fa=0;
            root=tree[pos].c[1];
        }
    }
}
LL finddata(int x)
{
    int y=root;
    while (x<=tree[tree[y].c[0]].size||x>tree[tree[y].c[0]].size+tree[y].num)
    {
        if (x<=tree[tree[y].c[0]].size) y=tree[y].c[0];
        else  
        {
            x-=tree[tree[y].c[0]].size+tree[y].num;
            y=tree[y].c[1];
        }
    }
    return tree[y].data;
}
LL ssum[110000];
int n,k,m;
int main()
{
    while (scanf("%d %d %d",&n,&k,&m)==3)
    {
        root=sum=num=0;
        for (int i=1;i<=n;i++)
            scanf("%I64d",&v[i]);
        for (int i=1;i<=n;i++)
            ssum[i]=ssum[i-1]+v[i];
        for (int i=1;i<=m;i++)
        {
            scanf("%d %d",&l[i],&r[i]);
            num++;
            a[num].id=i;a[num].x=l[i];a[num].type=1;
            num++;
            a[num].id=i;a[num].x=r[i];a[num].type=3;
        }
        for (int i=1;i<=n;i++)
        {
            num++;
            a[num].id=i;
            a[num].x=i;
            a[num].type=2;
        }
        sort(a+1,a+1+num,cmp);
        int now=0;
        LL ans=0;
        for (int i=1;i<=num;i++)
        {
            if (a[i].type==1)
            {
                now++;
                insert(ssum[r[a[i].id]],++sum);
            }
            if (a[i].type==2)
            {
                if (now<k) continue;
                ans=max(ans,finddata(now+1-k)-ssum[a[i].id-1]);
            }
            if (a[i].type==3)
            {
                now--;
                del(ssum[r[a[i].id]]);
            }
        }
        printf("%I64d\n",ans);
    }           
    return 0;
}

1006 中位数计数

首先我们能判断出,我们不需要特殊考虑两个数的平均值为 x 的这种情况。之后我们就可以将其转化为经典的问题了。枚举中位数,然后以之为起点,向两边扫,遇到大于x的数置为1,遇到小于 x 的数置为-1,遇到等于x的数置为0即可。然后判断有多少区间和为0即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>

using namespace std;

typedef long long LL;
int n;
int a[9000],b[9100];
int d[18100];
const int bb=9040;
int main()
{
    while (scanf("%d",&n)==1)
    {
        for (int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for (int i=1;i<=n;i++)
        {
            int ans=0;
            memset(d,0,sizeof(d));
            for (int j=1;j<=n;j++)
            {
                if (a[j]==a[i]) b[j]=0;
                else if (a[j]>a[i]) b[j]=1;
                else b[j]=-1;
            }
            int now=0;
            for (int j=i;j>=1;j--)
            {
                now+=b[j];
                d[now+bb]++;
            }
            now=0;
            for (int j=i;j<=n;j++)
            {
                now+=b[j];
                ans+=d[-now+bb];
            }
            printf("%d",ans);
            if (i!=n) printf(" ");
            else printf("\n");
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值