题意很明确了。所以直接跳到思路。
Sol 1
最暴力的写法是直接枚举每个参数(W)。有分,但显然无法AC。
Sol 2
注意到(Y)具有单调性,即随着(W)增大而减小。因此可以采用二分求出最靠近标准值(S)的两个值。程序段如下
bool ok1(long long x)
{
long long ans=check(x);//ans求的是Y的值
if (ans<S) return 0;else return true;//若y过大,则x取小了
}
.....
int main()
{
long long L=1,R=1000000;
while (L+1<R)
{
long long mid=(L+R)/2;
if (ok1(mid)) L=mid;else R=mid;
}//普通的二分
}
利用该方法,时间复杂度会大大减小。
Sol 3
我们的耗时大部分花在了求和,而无论是(sum_j v_j)还是(sum_j 1) 都具有可减性,故我们可以采用前缀和来维护这两项,这样可以用(O(n))修改前缀和,(O(m))查询,时间复杂度是质的飞跃。代码如下
#include<bits/stdc++.h>
using namespace std;
long long n,m,S,vis[300000],s[300000],l[300000],r[300000],v[300000],w[300000];
long long check(long long x)
{
long long ans=0;
for (long long i=1;i<=n;i++)
{
if (w[i]>=x) vis[i]=vis[i-1]+1,s[i]=s[i-1]+v[i];
else vis[i]=vis[i-1],s[i]=s[i-1];//判断的同时维护前缀和
}
for (long long i=1;i<=m;i++)
{
ans+=(vis[r[i]]-vis[l[i]-1])*(s[r[i]]-s[l[i]-1]);//O(1)求答案
}
return ans;
}
bool ok1(long long x)
{
long long ans=check(x);
if (ans<S) return 0;else return true;//上文已出现过
}
int main()
{
cin>>n>>m>>S;
for (long long i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
}
for (long long i=1;i<=m;i++)
{
cin>>l[i]>>r[i];
}
long long L=1,R=1000000;
while (L+1<R)
{
long long mid=(L+R)/2;
if (ok1(mid)) L=mid;else R=mid;
}//前面已出现过
cout<<min(check(L)-S,S-check(R))<<endl;//以L为参数的答案大于S,以R为参数的小于S
return 0;
}
后记
本题还有一道类似的题目P1083 借教室,同为NOIP题,有兴趣的同学可以写写。