洛谷P1250 种树(差分约束)

传送门

题目描述

一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民想在门前种些树并指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量

写一个程序完成以下工作:

输入输出格式

输入格式: 第一行包含数据N,区域的个数(0 < N ≤ 30000);

第二行包含H,房子的数目(0 < H≤5000);

下面的H行描述居民们的需要:B E T,0 < B≤E≤30000,T≤E-B+1。

输出格式: 输出文件只有一行写有树的数目

输入输出样例

输入样例#1:
9
4
1 4 2
4 6 2
8 9 2
3 5 2
输出样例#1:
5


这道题贪心可以做,树状数组 也可以做。然而我们来讲一下差分约束。
简单算法↑ 和 数据结构↑ 比较无聊。然后我们来讲差分约束→_→

这个思想非常有趣~~~下面是精彩的思想部分:

比较重要的地方我在题目描述中用加粗字体标注出来了。

首先,我们知道最短路算法跑完后,对于每一条边满足:
dis[to] <= dis[from] + cost ->①

不知道最短路是啥?

然后,设sum[i]为某个点到起点之间种了几棵树。(起点在哪自己定义,这个题中起点是0)
假设我们已经种完树了,那么必定有sum[E] - sum[B-1] >= T (E、B、T的定义去题面里找)
移项后易得:sum[B-1] <= sum[E] -T -> ②

将①式和②式比对一下,发现了什么→_→
竟然能够对应起来!好玩吧?

既然这个题我们要把区间维护成sum[B-1] <= sum[E] -T 这个样子,那么可不可以用最短路做呢?

关键点来了!

如果要跑最短路, 那么怎么建图?

1、有一种边我们是可以确定要建的,那就是①式对应的②式中的E和B-1,权值为-T。很显然,观察下①、②式就看出来了。
从 E 向 B-1 建一条边权为 -T的边。**

2、由条件“每个部分为一个单位尺寸大小并最多可种一棵树”可推导出重要条件:
1 >= sum[i] - sum[i-1] >=0
将这个式子移项变形得到两个式子:
sum[i] <= sum[i-1] +1 ->③
sum[i-1] <= sum[i] +0 ->④

由③可得:从i-1向i建一条边权为1的边。**
由④可得:从i向i-1建一条边权为0的边。**

3、显然 sum[i] <= sum[n+1] +0 -> ⑤
所以从sum[n+1] 向 sum[i] 建一条边权为0的边。**

条件T≤E-B+l 保证了图中没有负环。


建边完成,从n+1开始跑最短路就可以了。
跑完之后-sum[0]就是答案。

Dijkstra在处理负权时的表现非常差(即使这道题没有负环),所以我们用SPFA来跑最短路。

为什么从n+1开始跑而不是从0开始?

首先,观察⑤式,在每一条边中,from是n+1 ,to是i ;我们要保证式子成立就要从n+1开始跑。
其次,我们建的边肯定要有意义,而通过上面建边的过程可以得出,n+1这个点是没有入度的,除n+1外任何一个点都不可能跑到n+1,所以要从n+1开始跑。

为什么取sum[0]?

根据上一条好好想想。

另外,这个题可以改成跑最长路

怎么改?把所有的边反过来,负权变为正权,正权变为负权,其余不变。
最后sum[0]是答案。


此题关键在于如何建边,在理解了思想之后自己动手试一下,不等式要自己推导出来,切忌背代码!!


代码中dis数组就是sum数组。

#include
using namespace std;
const int maxn=200005;
int n,k,tot,m,ans,head[maxn],nxt[maxn<<1],dis[maxn];
struct Edge{
    int f,t,c;
}edge[maxn<<1];
inline int read()
{
    char ch;
    int nm=0;
    bool o=false;
    while((ch=getchar())<'0'||ch>'9')if(ch=='-')o=true;
    nm=ch-'0';
    while((ch=getchar())>='0'&&ch<='9')
        nm=nm*10+ch-'0';
    if(o)return -nm;
    return nm;
}
void build(int f,int t,int c)
{
    edge[++tot]=(Edge){f,t,c};
    nxt[tot]=head[f];
    head[f]=tot;
}
bool used[maxn];
queue<int>q;
void spfa(int s)
{
    memset(dis,0x7f,sizeof(dis));
    dis[s]=0;
    q.push(s);
    used[s]=true;
    while(!q.empty())
    {
        int num=q.front();q.pop();
        used[num]=false;
        for(int i=head[num];i;i=nxt[i])
        {
            int u=edge[i].f,v=edge[i].t;
            if(dis[v] > dis[u] + edge[i].c)
            {
                dis[v]=dis[u]+edge[i].c;
                if(!used[v])
                {
                    q.push(v);
                    used[v]=true;
                }
            }
        }   
    }   
}
int main()
{
    n=read();
    m=read();
    build(n+1,0,0);
    for(int i=1;i<=n;i++)//建边
    {
        build(i,i-1,0);
        build(i-1,i,1);
        build(n+1,i,0);
    }
    for(int i=1;i<=m;i++)
    {
        int f,t,c;
        f=read(),t=read(),c=read();
        build(t,f-1,-c);
    }
    spfa(n+1);
    printf("%d",-dis[0]);
    return 0;
}

你可能感兴趣的:(最短路,差分约束)