P3028 [USACO10OCT]汽水机Soda Machine
题意翻译
为了满足fj所有的N(1<=n<=50000)头奶牛的需求,fj新买了一台汽水机。他想找到一个最完美的位置来安放它。
奶牛的牧场可以被表示为一个一维数轴,第i个奶牛被放牧的区间是[Ai…Bi](包含端点),fj可以把汽水机放在[1…1,000,000,000]。
因为奶牛们都懒得要死,她们想尽可能的少移动。她们希望汽水机被放在自己的放牧区间内。
遗憾的是,fj并不总能满足所有奶牛的要求,所以他想请你帮忙算出他能满足的奶牛数目
输入输出样例
输入 #1
4
3 5
4 8
1 2
5 10
输出 #1
3
记录我第一次写博客和第一次在洛谷在做题(小菜鸡一枚)
当看到这道题时,我第一想法是暴力求解,遍历最小到最大的位置区间 i,然后取得最大值:
#include<iostream>
#include<stdio.h>
using namespace std;
struct Node{
int max,min;
};
int main()
{
int n;
int MAX = 0;
int MIN = 1000000000;
int ans = 0;
Node node[50005];
cin>>n;
for(int i = 0;i < n;i++)
{
scanf("%d",&node[i].min);
scanf("%d",&node[i].max);
if(node[i].max > MAX)
MAX = node[i].max;
if(node[i].min < MIN)
MIN = node[i].min;
}
for(int i = MIN;i <= MAX;i++)
{
int num = 0;
for(int j = 0;j < n;j++)
{
if(i >= node[j].min && i <= node[j].max)
{
num++;
}
}
if(num > ans)
ans = num;
if(num == n)
break;
}
cout<<ans;
}
很显然,由于题目所给的数值过大,几乎一半数据超时了,只有54分。
怎么办呢,我们观察可以发现,母牛的分布的位置区间有个跨度(例如母牛a在3~10086之间活动)其中的间隙有多大,对于我们解题没有帮助,所以我们可以尝试将位置区间换个数字,缩小间距,即离散化
举个例子:
母牛a(3,100) 母牛b(7,50) 母牛c(50,100)
题目中只有四个有用数据:3,7,50,100
我们将等效其转变为1,2,3,4
那么母牛a(1,4) 母牛b(2,3)母牛c(3,4)
然后设一个num[100000],计算对每个单独地点有多少母牛能到达,取最大值,就能省去很多遍历
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
struct Node{
int x,i,z;
bool operator<(const Node &k){
return x < k.x;
}
};
int main()
{
int n;
int ans = 0;
Node node[100010];
int num[100010];
int sum[100010];
cin>>n;
for(int i = 0;i < n;i++)
{
scanf("%d",&node[2 * i].x);
scanf("%d",&node[2 * i + 1].x);
node[2 * i].i = 2 * i;
node[2 * i + 1].i = 2 * i + 1;
}
sort(node,node + 2 * n);
node[0].z = 1;
for(int i = 1;i < 2 * n;i++)
{
if(node[i].x == node[i-1].x)
node[i].z = node[i-1].z;
else
node[i].z = node[i-1].z + 1;
}
for(int i = 0;i < 2 * n;i++)
{
num[node[i].i] = node[i].z;
}
for(int i = 0;i < n;i++)
{
//cout<<num[2 * i]<<' '<<num[2 * i + 1]<<endl;
for(int j = num[2 * i];j <= num[2 * i + 1];j++)
{
sum[j]++;
if(sum[j] > ans)
ans = sum[j];
}
}
cout<<ans;
}
然而还是没有满分,只有72分。
代码在最后的 计算每个格子能存在的最大母牛 上能做优化。
引入差分数组的概念
定义:
对于已知有n个元素的数列d,建立记录它每项与前一项差值的差分数组f:显然,f[1]=d[1]-0=d[1];对于整数i∈[2,n],我们让f[i]=d[i]-d[i-1]。
计算数列各项的值:观察d[2]=f[1]+f[2]=d[1]+d[2]-d[1]=d[2]可知,d[i]=f[i]的前缀和。
快速处理区间加减操作:
对数列区间[L,R]中的每个数加上x,我们通过性质(1)知道,第一个受影响的差分数组中的元素为f[L],即令f[L]+=x,那么后面数列元素在计算过程中都会加上x;
最后一个受影响的差分数组中的元素为f[R],所以令f[R+1]-=x,即可保证不会影响到R以后数列元素的计算。
这样我们不必对区间内每一个数进行处理,只需处理两个差分后的数即可;
(摘自https://www.cnblogs.com/young-children/p/11749156.html.)
简单的来说,我们原本应该对num数组中3~8号元素+1,然后遍历num数组取最大值,num记录的就是有多少母牛能到的数目。现在num改为记录与上一位元素的差值num[ i ] - num[ i - 1] ,i 点的母牛数就是 ∑ k = 1 n n u m [ k ] \displaystyle \sum_{k=1}^n num[ k ] k=1∑nnum[k] ,对3~8号元素+1就是num[ 3 ]++,num[ 9 ] - -。
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
struct Node{
int x,i,z;
bool operator<(const Node &k){
return x < k.x;
}
};
int main()
{
int n;
int ans = 0;
Node node[100010];
int num[100010];
int sum[100010];
cin>>n;
for(int i = 0;i < n;i++)
{
scanf("%d",&node[2 * i].x);
scanf("%d",&node[2 * i + 1].x);
node[2 * i].i = 2 * i;
node[2 * i + 1].i = 2 * i + 1;
}
sort(node,node + 2 * n);
node[0].z = 1;
for(int i = 1;i < 2 * n;i++)
{
if(node[i].x == node[i-1].x)
node[i].z = node[i-1].z;
else
node[i].z = node[i-1].z + 1;
}
for(int i = 0;i < 2 * n;i++)
{
num[node[i].i] = node[i].z;
}
for(int i = 0;i < n;i++)
{
//cout<<num[2 * i]<<' '<<num[2 * i + 1]<<endl;
/*for(int j = num[2 * i];j <= num[2 * i + 1];j++)
{
sum[j]++;
if(sum[j] > ans)
ans = sum[j];
}*/
sum[num[2 * i]]++;
sum[num[2 * i + 1] + 1]--;
}
int s = 0;
for(int i = 1;i <= 2 * n;i++)
{
s += sum[i];
if(s > ans)
ans = s;
//cout<<s<<' '<<sum[i]<<endl;
}
cout<<ans;
}
这样就有100分了。