jzoj1295,P1607-轻轨(庙会班车)【贪心,线段树】

前言

我考试时敲了一个不仅比正解编程复杂度高,而且时间更慢,还AC不了的费用流

垃圾代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define MN 20011
using namespace std;
struct node{
    int to,w,next,c;
}a[150001];
queue<int> q;
int tot,k,n,c,s,e,x,y,w,sum;
int ls[MN],maxf[MN],b[MN],f[MN],v[MN];
void addl(int x,int y,int w,int c)
{
    a[++tot].to=y;a[tot].w=w;
    a[tot].next=ls[x];ls[x]=tot;
    a[tot].c=c;
    a[++tot].to=x;a[tot].w=0;
    a[tot].next=ls[y];ls[y]=tot;
    a[tot].c=-c;
}
bool spfa()
{
    memset(f,-127/3,sizeof(f));
    while (!q.empty()) q.pop();
    q.push(s);v[s]=true;f[s]=0;maxf[s]=2147483647;
    while (!q.empty())
    {
        int x=q.front();
        v[x]=false;
        q.pop();
        for (int i=ls[x];i;i=a[i].next)
        {
            int y=a[i].to;
            if (a[i].w&&f[x]+a[i].c>f[y])
            {
                f[y]=f[x]+a[i].c;
                maxf[y]=min(maxf[x],a[i].w);
                b[y]=i;
                if (!v[y])
                {
                    q.push(y);
                    v[y]=true;
                }
            }
        }
    }
    if (f[e]>-690563370) return true;
    else return false;
}
int updata()
{
    x=e;
    int ans=0;
    while (x!=s)
    {
        a[b[x]].w-=maxf[e];
        a[b[x]^1].w+=maxf[e];
        ans+=a[b[x]].c;
        x=a[b[x]^1].to;
    }
    return ans*maxf[e];
}
int main()
{
    tot=1;
    scanf("%d%d%d",&k,&n,&c);
    s=n+1;e=s+1;
    addl(s,1,c,0);addl(n,e,c,0);
    for (int i=1;i<n;i++) addl(i,i+1,c,0);
    for (int i=1;i<=k;i++)
    {
        scanf("%d%d%d",&x,&y,&w);
        addl(x,y,w,1);
    }
    while (spfa()) 
      sum+=updata();
    printf("%d",sum); 
}
//If you want to AK(IOI)!
//first:
//     AK        |      AK IOI
//sto         orz|sto         orz
//sto         orz|sto         orz
//sto   xjq   orz|sto   hzb   orz
//sto         orz|sto         orz
//sto         orz|sto         orz
//Then you will AK(IOI)!
//不要问我为啥会有上面的东西

大意

一个列车可以坐 C C 头牛,有K组牛,每组 mi m i 个,从 si s i 出发到 ei e i 。求最多可以带多少头牛。


解题思路

首先众所周知可以用数学归纳法证明每次选取结束时间最早的是最优解
自己理解的证明:
选结束时间晚的只有两种情况

1.
这里写图片描述
这种情况绿色比红色占的范围更大,肯定不一定最优解。


2.
这里写图片描述
这种情况由于被红色线段占了的已经计算过了,如果计算红色的话那么前面的一定没有被红色覆盖,所以按照这么算其实就是这样
这里写图片描述
这样的话绿色占的范围依旧比红色大,所以依旧不一定是最优解。

好了,证明完了之后就可以先按照结束位置排个序,用 (i,i) ( i , i ) 这个区间来表示在 i i <script type="math/tex" id="MathJax-Element-238">i</script>这个时间点车子计算到现在空闲的容量,然后枚举将每个开始到结束区间尽量减到0(能减就减,当然注意这个组的人数)


代码

#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
    int star,end,w;
}w[50001];
struct treenode{
    int l,r,w,lazy;
}a[80001];
int k,n,c,s;
void build(int x,int l,int r,int num)
{
    a[x].l=l;a[x].r=r;a[x].w=num;
    if (l==r) return;
    int mid=(l+r)/2;
    build(x*2,l,mid,num);
    build(x*2+1,mid+1,r,num);
}
void ddata(int x)
{
    if (a[x].lazy)
    {
      a[x*2].w+=a[x].lazy;
      a[x*2+1].w+=a[x].lazy;
      a[x*2].lazy+=a[x].lazy;
      a[x*2+1].lazy+=a[x].lazy;
      a[x].lazy=0;
    }
}
void updata(int x,int l,int r,int num)
{
    if (a[x].l==l&&a[x].r==r)
    {
        a[x].w+=num;
        a[x].lazy+=num;
        return;
    }
    ddata(x);
    if (a[x*2].r>=r) updata(x*2,l,r,num);
    else if (a[x*2+1].l<=l) updata(x*2+1,l,r,num);
    else updata(x*2,l,a[x*2].r,num),updata(x*2+1,a[x*2+1].l,r,num);
    a[x].w=min(a[x*2].w,a[x*2+1].w);
}
int find(int x,int l,int r)
{
    if (a[x].l==l&&a[x].r==r)return a[x].w;
    ddata(x);
    if (a[x*2].r>=r) return find(x*2,l,r);
    else if (a[x*2+1].l<=l) return find(x*2+1,l,r);
    else
    { 
      a[x].w=min(a[x*2].w,a[x*2+1].w);
      return min(find(x*2,l,a[x*2].r),find(x*2+1,a[x*2+1].l,r));
    }
}
bool cmp(node x,node y)
{
    return x.end<y.end;
}
int main()
{
    scanf("%d%d%d",&k,&n,&c); 
    for (int i=1;i<=k;i++)
    {
        scanf("%d%d%d",&w[i].star,&w[i].end,&w[i].w);
    }
    build(1,1,n,c);//建树
    sort(w+1,w+1+k,cmp);//排序
    for (int i=1;i<=k;i++)
    {
        int h=find(1,w[i].star,w[i].end-1);
        if (h)//是否可以接牛
        {
            updata(1,w[i].star,w[i].end-1,-min(h,w[i].w));//修改区间
            s+=min(h,w[i].w);//累计答案
        }
    }
    printf("%d",s);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值