大意:
思路:
首先,对于每一个有人的位置,它能覆盖的区域就是它与讲台两点的连线对应的在他背后的区域(红色斜线部分),对于每一个点都做出这样一个区域之后,我们就得到了一个完整的覆盖区域,我们的目标自然就是描绘出它的边缘(图中黑线),那么每一行黑线之前的点(图中绿点)就是ok的部分。
现在问题转化为找出每一行黑线覆盖的最左点。仔细观察的话不难发现,如果我们只考虑斜向上的斜线的话,从下到上它们的斜率是依次增大的,而对于每一行,一个斜率大的斜线肯定会覆盖斜率小的斜线,所以我们只需要动态维护从(0,1)点出发的最大斜率即可,每次更新最值。从上到下同理。
这样的话,对于每一次询问,我们可以在O(n)的时间内完成处理,时间是ok的
tip:为了防止保存斜率时double类型带来的计算上的不遍,我选择用x_as,y_as来保存,它们都是ll类型,这样求斜率时只要除一下就好了
别的也没了,这题也并不是很难~
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=2e5+10;
set<ll> st[N];
ll ans[N];//记录答案
ll minx[N];
ll n,m,k,q;
ll a,b,c;
pair<ll,ll> sd[N];
double lim[N];
void work()
{
//cout<<"ON"<<endl;
ll sum=0;//统计
for(int i=1;i<=m;++i) ans[i]=n;
ll x_as=1,y_as=1;//维护最大斜率
for(int i=1;i<=m;++i)
{
//if(st[i].size()==0) continue;
//cout<<i<<" "<<*st[i].begin()<<endl;
if(st[i].size()) ans[i]=min(ans[i],*st[i].begin()-1);
if(i==1||i==m) continue;
//(ans[i]+1,i)
if((i-1)*x_as>(y_as-1)*(ans[i]+1))
{
x_as=ans[i]+1;
y_as=i;
}
// cout<<i<<' '<<x_as<<' '<<y_as<<endl;
ll real_x_as=i*x_as/(y_as-1);
if(real_x_as*(y_as-1)==i*x_as) real_x_as--;
// cout<<real_x_as<<endl;
ans[i+1]=min(ans[i+1],real_x_as);
// cout<<i+1<<' '<<ans[i+1]<<endl;
//cout<<i+1<<" "<<ans[i+1]<<endl;
}
x_as=1,y_as=m;
//for(int i=1;i<=m;++i) cout<<ans[i]<<endl;
for(int i=m;i;--i)
{
if(st[i].size()) ans[i]=min(ans[i],*st[i].begin()-1);
if(i==1||i==m) continue;
if((m-i)*x_as>(ans[i]+1)*(m-y_as))
{
y_as=i;
x_as=ans[i]+1;
}
ll real_x_as=(((m-(i-1))*x_as)/(m-y_as));
if(real_x_as*(m-y_as)==x_as*(m-i+1)) real_x_as--;
ans[i-1]=min(ans[i-1],real_x_as);
}
for(int i=1;i<=m;++i) sum+=ans[i];
// cout<<"JLM"<<endl;
// for(int i=1;i<=m;++i) cout<<ans[i]<<endl;
cout<<sum<<endl;
}
int main()
{
cin>>n>>m>>k>>q;
for(int i=1;i<=k;++i)
{
cin>>a>>b;
st[b].insert(a);//每一行按x坐标排序
sd[i]=make_pair(a,b);
}
while(q--)
{
cin>>a>>b>>c;
ll x=sd[a].first;ll y=sd[a].second;
st[y].erase(x);//删除
st[c].insert(b);
sd[a]=make_pair(b,c);
work();
}
return 0;
}