开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
B - Hossam and Friends
题意:给出m个点对,找出1~n中不含点对的子数组的个数,比如1,2,3,4,5,有一个点对[1,3],那么[1,2,3]这个子数组就是不合法的,因为它包含了点对[1,3],但是[1,2],[2,3],[2,3,4,5]是合法的,以为他们都没有包含一个完整的点对
思路:一开始是想把点对所在的子数组求出来然后用总数减去这些不满足的就是答案了,但显然是很困难的,然后考虑怎样正着求,其实可以想一下,这些点对实际上相当于一个个的隔板,这个隔板只有两个数都存在时才会有效,那我们就考虑如何避开一个点对去求数组,比如这个例子
12 2
3 7
5 10
一开始我们发现7之前的每个子数组是都符合条件的,也就是6*(6+1)/2个,然后发现4-9也是可以的,也是6*(6+1)/2个,但是会发现4 5 6这三个数字被重复算了,那么就不能用推出来的公式直接算,需要一个一个的来考虑了,考虑用一个队列,每次加一个数答案就将上这个队列的size,结果还是一样的还是等差数列,不过是一个个加进去的,另外就需要考虑如果存放点对,对一个点对[u,v]来说对u最有价值的是最小的大于他的v,所以我们用一个suf数组表示点对[i,suf[i]]suf[i]是最小的大于i的数,然后用一个优先队列来维护最小的suf[i],当然可能有重复的suf[i],那么有价值的是最大的i,修改一下排序规则就可以,另外因为有重复所以弹出的时候要弹干净
#include<bits/stdc++.h>
#define int long long
using namespace std;
const ll mod=998244353;
const ll inf=1e18;
const int N=1e6+100;
struct node
{
int id,val;
bool operator<(const node &a)const
{
if(a.val==val) return a.id>id;
return a.val<val;
}
};
int t,n,m,suf[N],a[N];
signed main()
{
cin>>t;
for(int i=1;i<=100000;i++) suf[i]=inf;
while(t--)
{
int cnt=0;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
if(u>v) swap(u,v);
suf[u]=min(suf[u],v);
a[++cnt]=u;
a[++cnt]=v;
}
priority_queue<node>q;
queue<int>q1;
int las=0,ans=0;
for(int i=1;i<=n;i++)
{
q1.push(i);
//cout<<"q1.size="<<q1.size()<<" i="<<i<<" q1.front"<<q1.front()<<endl;
ans+=q1.size();
if(suf[i]!=inf) q.push(node{i,suf[i]});
if(!q.empty()&&q.top().val==i+1)
{
int u=q.top().val;
while(q1.size()&&q1.front()<=q.top().id) q1.pop();
while(!q.empty()&&u==q.top().val) q.pop();
}
}
cout<<ans<<endl;
for(int i=1;i<=cnt;i++) suf[a[i]]=inf;
}
return 0;
}