bzoj1805: [Ioi2007]Sail 船帆

题意

自己看

题解

这题想了我一早上。。
看来还是我太菜了QAQ
但是感觉想出来的时候觉得他很简单嘛

设第i行的旗子数为 Hi H i
不难发现,答案和顺序是没有关系的,都是 Hi(Hi1)/2 H i ∗ ( H i − 1 ) / 2 的总和。。

于是我们稍作思考,发现一个很对的贪心策略
其实这题是这样的

现在有n个数,m个操作
这n个数一开始都是0
每个操作包含两个数,L,K (K≤L)
对于每一个操作,你需要将前L个数前k小的全部加1
如现在有个序列 1 2 3 4
操作为3 2
那么前3个数前k小的有1和2
所以出来的数为2,3,3,4
现在问你经过m次操作后每个数是多少
看不懂地可以看一下暴力的实现

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=10005;
int n;
struct qq
{
    int h,k;
}s[N];
bool cmp (qq a,qq b){return a.h<b.h;}
int h[N];//现在这个杆上有多少
struct qt
{
    int x,id;
}a[N];//用来找k大
bool cmp1 (qt a,qt b){return a.x<b.x;}
int main()
{
    scanf("%d",&n);
    for (int u=1;u<=n;u++)  scanf("%d%d",&s[u].h,&s[u].k);
    sort(s+1,s+1+n,cmp);
    for (int u=1;u<=n;u++)
    {
        int cnt=0;
        for (int i=1;i<=s[u].h;i++) 
        {
            a[++cnt].x=h[i];
            a[cnt].id=i;
        }
        sort(a+1,a+1+cnt,cmp1);
        /*for (int u=1;u<=cnt;u++)
            printf("%d %d\n",a[u].id,a[u].x);
        printf("%d\n",s[u].k);*/
        for (int i=1;i<=s[u].k;i++)
        {
        //  printf("YES:%d\n",a[i].id);
            h[a[i].id]++;
        }
        /*for (int u=1;u<=10;u++) printf("%d ",h[u]);
        printf("\n");*/
    }
    int ans=0;
    for (int u=1;u<=s[n].h;u++)
        ans=ans+(h[u]-1)*h[u]/2;
    printf("%d\n",ans);
    return 0;
}

自己yy一下就知道这个模型很对

于是我就在这个模型上面卡了两三个小时才弄出来
怎么弄呢?
我们可以人为地使得这个序列递减
那么每一次要访问的区间就知道了,我们就使得这一段区间+1
但是你会发现这会有一个问题,就是+1之后可能就使得区间不递减了
那怎么办呢?
我们不难想到,使得区间不递减的情况只有下面这一个
这里写图片描述

也就是端点的两端是同一个数,别的情况都是没有影响的
这样我们不难想到一个方法,就是使得上图中,端点右端的a不加,找到a的最左端点加,这样就没有问题了,容易知道,最左边的a加是不会有任何影响的
至于怎么找,怎么维护,就都是线段树上的问题了,细节问题看看代码吧

是不是很简单,是不是觉得我想了一早上很菜?

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const LL N=100005*2;
const LL MAX=1<<30;
LL n;
struct qq
{
    LL h,k;
}a[N];
bool cmp (qq a,qq b){return a.h<b.h;}
struct qt
{
    LL l,r;
    LL s1,s2;
    LL minn;//这段区间的最小值   最大值
    LL c;//区间加? 
}s[N*2];LL num;
LL maxx;
void bt (LL l,LL r)
{
    LL aa=++num;
    s[aa].l=l;s[aa].r=r;
     s[aa].c=0;
    if (l==r)
    {
        s[aa].minn=0;
        if (l==maxx) s[aa].minn=-MAX;
        return ;
    }
    LL mid=(l+r)>>1;
    s[aa].s1=num+1;bt(l,mid);
    s[aa].s2=num+1;bt(mid+1,r);
    s[aa].minn=min(s[s[aa].s1].minn,s[s[aa].s2].minn);
}
void push_down (LL now)
{
    LL c=s[now].c;s[now].c=0;
    LL s1=s[now].s1,s2=s[now].s2;
    s[s1].minn+=c;s[s1].c+=c;
    s[s2].minn+=c;s[s2].c+=c;
    return ;
}
LL find (LL now,LL x)//我现在需要寻找第x个数 
{
    if (s[now].l==s[now].r) return s[now].minn;
    push_down(now);
    LL mid=(s[now].l+s[now].r)>>1;
    LL s1=s[now].s1,s2=s[now].s2;
    if (x<=mid) return find(s1,x);
    else return find(s2,x);
}
LL find1 (LL now,LL x)//找到第一个小于等于这个数的位置 
{
    if (s[now].l==s[now].r)
        return s[now].l;
    push_down(now);
    LL mid=(s[now].l+s[now].r)>>1;
    LL s1=s[now].s1,s2=s[now].s2;
    if (s[s1].minn<=x) return find1(s1,x);
    else return find1(s2,x);
}
void change (LL now,LL l,LL r)
{
    if (s[now].l==l&&s[now].r==r)
    {
        s[now].minn++;s[now].c++;
        return ;
    }
    push_down(now);
    LL s1=s[now].s1,s2=s[now].s2;
    LL mid=(s[now].l+s[now].r)>>1;
    if (r<=mid) change(s1,l,r);
    else if (l>mid) change(s2,l,r);
    else change(s1,l,mid),change(s2,mid+1,r);
    s[now].minn=min(s[s1].minn,s[s2].minn);
}
LL ans=0;
void dfs (LL now)
{
    if (s[now].l==s[now].r)
    {
        if (s[now].l==maxx) return ;
        ans=ans+s[now].minn*(s[now].minn-1)/2;
        return ;
    }
    push_down(now);
    LL s1=s[now].s1,s2=s[now].s2;
    dfs(s1);dfs(s2);
}
int main()
{
    scanf("%lld",&n);
    for (LL u=1;u<=n;u++)
        scanf("%lld%lld",&a[u].h,&a[u].k);
    sort(a+1,a+1+n,cmp);maxx=a[n].h+1;
    bt(1,maxx);
    for (LL u=1;u<=n;u++)//插入 
    {
        LL t=a[u].h-a[u].k+1;//我们要的范围是哪里
        LL ooo=find(1,t);//找到最前面的那个数
        LL ooo1=find1(1,ooo-1);//找到这个东西的右端点
        LL ooo2=find1(1,ooo);//找到这个东西的左端
        LL lalal=min(a[u].k,ooo1-t);//看下有多少个数在这个范围里面 
        if (ooo1<=a[u].h) change(1,ooo1,a[u].h); 
        change(1,ooo2,ooo2+lalal-1);
    }
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值