CF 652 C(思维好题) D(排序+BIT维护) E(边双联通分量+缩点).

题意:给出[1.n]排列的序列a,m对数(xi,yi) 定义区间合法为:区间不能包含着m对数中的任意一对,
即x[i],y[i]不能同时出现在一个区间,问有多少个区间为合法的? n,m<=3e5,x[i]!=y[i].


首先注意到若[l,r]为合法区间 则[l+1,r]肯定也为合法区间.尝试用two pointer解决 发现正着做好麻烦 移动l时还要删除操作.

[l,r]为合法,[l,r+1]为非法 则l-1开头的左端点 最多到r.此时右端点取决于min(r,f[i])..倒着做.

只要记录i为左端点时,后面和a[i]碰到的第一个非法位置a[j], 若[l,r]为合法,则k属于[l,r]->f[a[k]]>r

则从后往前枚举左端点 维护最远能到的rg(是非递增的)即可.O(n+m).

#include <bits/stdc++.h>
using namespace std; 
typedef long long ll;
const int N=5e5+5,inf=0x3f3f3f3f;
int n,m,a[N],pos[N];
vector<int> f[N];
int main()
{	
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),pos[a[i]]=i;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		int px=pos[x],py=pos[y];
		if(px>py)
			swap(px,py);
		f[px].push_back(py);
	}
	int rg=n+1;
	ll ans=0;
	for(int l=n;l>=1;l--)
	{
		for(int j=0;j<f[l].size();j++)
			rg=min(rg,f[l][j]);
		ans+=rg-l;
	}
	cout<<ans<<endl;
    return 0;
}
题意:n个线段[li,ri] 端点不会重合,问第i条线段包含多少条其他的线段[i=1,2..n]. n<=2e5,-1e9<=l[i],r[i]<=1e9
将左端点l从大到小排序,则被第i个线段包含的线段j只可能在i之前 l[j]>l[i] 只要求出有多少个r[j]<r[i]的即可.
离散化后树状数组记录R[j] 求一次r[i]前缀和即可 O(NlogN).

#include <bits/stdc++.h>
using namespace std; 
typedef long long ll;
const int N=4e5+5,inf=0x3f3f3f3f;
int n,c[N];
struct node{
	int l,r,id;
}a[N];
bool cmp(node a,node b)
{
	return a.l>b.l;
}
vector<int> v;
int getid(int x)
{
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int res[N];
int lowbit(int x)
{
	return x&-x;
}
void update(int x,int val)
{
	for(int i=x;i<N;i+=lowbit(i))
		c[i]+=val;
}
int query(int x)
{
	int res=0;
	for(int i=x;i>0;i-=lowbit(i))
		res+=c[i];
	return res;
}
int main()
{	
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&a[i].l,&a[i].r),v.push_back(a[i].l),v.push_back(a[i].r),a[i].id=i;
    sort(a+1,a+1+n,cmp);
    sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	for(int i=1;i<=n;i++)
	{
		int id=getid(a[i].r);
		int sum=query(id);
		res[a[i].id]=sum;
		update(id,1);
	}
	for(int i=1;i<=n;i++)
		printf("%d\n",res[i]);
	return 0;
}

题意:n点m边联通图,边的权值为0或者1.问从a开始能否经过至少一条权值为1的边然后到达b,每条边只能走一次(中间可以经过b)
n,m<=3e5.
利用边双联通分类性质.
边双联通分量.
性质1:无桥(删除任意一条边,不改变其联通性).
性质2:任意两点有至少有两条不相交路径(路径上的边都不同.反证:若a-b两个路径有相同边存在,删掉这个相同边 则a-b不联通.)

把原图中的边双联通分量进行缩点,一个边双联通分量->新点 则该点点权为1当且仅当边双联通分量中有权为1的边存在.
现在原图变为一颗树 a-b只有一条路径存在.

看a-b路径上是否有点/边权值为1的存在.在树上跑最长路(每点经过一次)即可.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> ii;
const int N=6e5+20,inf=0x3f3f3f3f;
struct Graph{
	int h[N],p[N],fr[N],v[N],w[N],val[N],dis[N],vis[N],cnt,id,n;
	int dfn[N],low[N],instack[N],stack[N],belong[N],bcc,top;
	Graph():cnt(0){}
	void add(int a,int b,int c)
	{
		p[++cnt]=h[a],fr[cnt]=a,v[cnt]=b,w[cnt]=c,h[a]=cnt;
		p[++cnt]=h[b],fr[cnt]=b,v[cnt]=a,w[cnt]=c,h[b]=cnt;
	}
	void tarjan(int u,int fa)
	{
		dfn[u]=low[u]=++id;
		instack[u]=1,stack[++top]=u;
		for(int i=h[u];i;i=p[i])
		{
			if(v[i]==fa)	continue;
			if(!dfn[v[i]])
			{
				tarjan(v[i],u);
				low[u]=min(low[v[i]],low[u]);
			}
			else if(instack[v[i]])
				low[u]=min(low[u],dfn[v[i]]);
		}
		int i;
		if(dfn[u]==low[u])
		{
			++bcc;
			do{
				i=stack[top--];
				belong[i]=bcc;
				instack[i]=0;
			}while(i!=u);
		}
	}
	void do_bcc()
	{
		id=0,top=0,bcc=0;
		for(int i=1;i<=n;i++)
			if(!dfn[i])
				tarjan(i,0);
	}
	void rebuild(Graph &out)//
	{
		for(int i=1;i<=cnt;i+=2)
		{
			if(belong[fr[i]]!=belong[v[i]])
				out.add(belong[fr[i]],belong[v[i]],w[i]);
			else if(w[i])
				out.val[belong[fr[i]]]=1;
		}
	}
	bool calc(int x,int y)
	{
		queue<int> q;
		q.push(x);
		dis[x]=val[x],vis[x]=1;
		while(!q.empty())
		{
			int u=q.front();
			q.pop();
			for(int i=h[u];i;i=p[i])
			{
				if(!vis[v[i]])
				{
					if(dis[v[i]]<dis[u]+w[i]+val[v[i]])
						dis[v[i]]=dis[u]+w[i]+val[v[i]];
					vis[v[i]]=1,q.push(v[i]);
				}
			}
		}
		return dis[y];
	}
}base,ne;
int main()
{
	int n,m,i,a,b,c;
	scanf("%d%d",&n,&m);
	base.n=n;
	while(m--)
	{
		scanf("%d%d%d",&a,&b,&c);
		base.add(a,b,c);
	}
	scanf("%d%d",&a,&b);
	base.do_bcc();base.rebuild(ne);
	puts((ne.calc(base.belong[a],base.belong[b]))?"YES":"NO");
	return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值