【cs(plus)^(20)】20191022【模拟】【联通块分析】【思维分析消圈】

今天前两题都挺水的,,第三题考了个啥啊

woj4759到4761,

spongebob

求出每个直线和x轴的交点。很明显在指定点左边的,都是递增,右边都是递减。

那就直接模拟就好了。一开始都加上正k。然后每过一个分界点就改一个。取最小。

记得inf开的够大。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
struct node{
	double k,b,x;int id;
}seg[300003];
int n;
bool cm(node a,node b){
	return a.x<b.x;
}
signed main(){
	n=in;
	for(int i=1;i<=n;i++){
		seg[i].id=i;
		scanf("%lf %lf",&seg[i].k,&seg[i].b);
		seg[i].x=seg[i].b/seg[i].k;seg[i].x*=-1.0;
	}
	sort(seg+1,seg+n+1,cm);
	double  ksum=0,bsum=0,ans=0x3f3f3f3f3f3f;
	for(int i=1;i<=n;i++){
		if(seg[i].k>=0){
			ksum-=seg[i].k;bsum-=seg[i].b;
		}else ksum+=seg[i].k,bsum+=seg[i].b;
	}
	for(int i=1;i<=n;i++){
		if(seg[i].k>=0){
			ksum+=2.0*seg[i].k;bsum+=2.0*seg[i].b;
		}else{
			ksum-=2.0*seg[i].k;bsum-=2.0*seg[i].b;
		}if(ans>seg[i].x*ksum+bsum)ans=seg[i].x*ksum+bsum;
	}//if(fabs(ans-1061109567)<1.0)ans=1302450071.940088;
	printf("%.7lf",ans);
	return 0;
}

patrick(纪念他)

前置结论:在没有环的前提下,在一个图里加一条边,联通块数减一。即连通块数=点数-边数

很显然,并查集就是这样的。

对于这道题,就是求指定高度以上的联通块数。

如果每个岛向两边连边,那便满足无环。

所以我们只需要维护水面以下的岛屿数量,水面以上的活着的边的数量,n-这两个东西就是答案。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int t[500003],t2[500003],n,m,a[500003];char ch[3];
int lowbit(int x){
	return x&(-x);
}
void modify(int x,int key){
	while(x){
		t[x]+=key;x-=lowbit(x);
	}
}
void modify2(int x,int key){
	while(x<=500000){
		t2[x]+=key;x+=lowbit(x);
	}
} int last;
int query(int x){
	int sum=0;
	while(x<=500000){
		sum+=t[x];x+=lowbit(x);
	}return sum;
}
int query2(int x){
	int sum=0;
	while(x>0){
		sum+=t2[x];x-=lowbit(x);
	}return sum;
}
signed main(){
	n=in;m=in;a[0]=a[n+1]=0x3f3f3f3f;for(int i=1;i<=n;i++)a[i]=in;for(int i=1;i<=n;i++)modify2(a[i],1);
	for(int i=1;i<n;i++)modify(min(a[i],a[i+1]),1);
	while(m--){
		scanf("%s",ch+1);
		if(ch[1]=='Q'){
			int x=in^last;
			last=n-query(x)-query2(x-1);
			cout<<last<<'\n';
		}else{
			int x=in^last;int y=in^last;
			if(x>1)modify(min(a[x],a[x-1]),-1);
			if(x<n)modify(min(a[x],a[x+1]),-1);
			modify2(a[x],-1);
			a[x]=y;
			if(x>1)modify(min(a[x],a[x-1]),1);
			if(x<n)modify(min(a[x],a[x+1]),1);
			modify2(a[x],1);
		}
	}
	return 0;
} 

eugene

这道题,,我鈤。

辣鸡题,指的是做了之后觉得自己是个辣鸡。

需要详细解释。

这道题有个结论:如果ab,bc,有边,那我可以ac连边然后删掉前面两个。

如果有环,可以直接删掉。

如果有重边,可以直接删掉。

上面都是针对同一个权值来说的。

如果我们维护一种在线的加边删边算法。

具体过程是这样的。

这个算法是在线的,我们需要维护以下东西。(均只针对一种权值)

id:与该点相连的边的编号。

to:这条边对面的点号。

d:这条边对于我来说,是加贡献,还是减贡献(相当于钦定了方向)根据题意,我们规定0表示这个点得到贡献,1表示这个点减去贡献。

另外还要维护depend,rev,因为有些边会被删掉,所以要记录这条边的答案是从哪个虚边来的。

因为这些边之间钦定的方向会产生冲突,所以记录是否异或1.reverse。

 

加边流程:

分类讨论

首先,我们钦定x向y连边。所以接下来根据这个钦定确定reverse方向。

如果两个点之间有重边,直接删除。两条边的答案一个0一个1。

如果两个点在过去都没有边,直接将两点边编号设为当前边(flag的用处)

如果x,y曾经有边,就会产生需要删除的情况。

他们是独立的,各自删除。

因为方向是x向y。所以x原来的边应该是指向x。如果不是,就应该reverse=1。

而对于y来说,情况恰好相反。本来应该向外的。如果指向自己,就应该reverse=1。

如果删掉了这个边,那x和y都走到自己原来边对面那个点。因为虚边连的是这个点。

然后将两个点的对面设好,钦定了一个方向,根据flag分类讨论id编号。

 

对于work阶段。

1和2分别处理后,一个点最多有1个权值1的边,1个权值2的边。这两条边对于这个点的方向应该是相反的。

而且根据这张图我们发现,对于任何一个点,方向是相同的,所以直接while走。遇到环return。

最后该reverse 就 reverse。

输出答案。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}

struct node{
	int id,to,d;
}a[2][2000003];
int n,m;
int depend[2000003];
int rev[2000003];
int ans[2000003];int tot;
void add(int x,int y,int id,int r){
	node*q=a[r];
	if(q[x].id&&q[x].to==y){
		depend[q[x].id]=depend[id]=0;
		ans[id]=1;ans[q[x].id]=q[y].d;
		q[x].id=q[y].id=0;
		return;
	}
	int flag=1;
	if(q[x].id){
		flag=0;
		depend[q[x].id]=tot;
		rev[q[x].id]=q[x].d;
		q[x].id=0;
		x=q[x].to;
	}
	if(q[y].id){
		flag=0;
		depend[q[y].id]=tot;
		rev[q[y].id]=q[y].d^1;
		q[y].id=0;
		y=q[y].to;
	}
	q[x].to=y;q[y].to=x;
	q[x].d=1;q[y].d=0;
	if(flag)q[x].id=q[y].id=id;
	else{
		q[x].id=q[y].id=depend[id]=tot++;
	}
}
int vis[2000003];
void work(int u){
	int x=u,t=0;
	vis[x]=1;
	while(a[t][x].id){
		ans[a[t][x].id]=a[t][x].d;
		if(vis[a[t][x].to])return;
		vis[a[t][x].to]=1;
		x=a[t][x].to;t^=1;
	}
	x=u;t=1;
	while(a[t][x].id){
		ans[a[t][x].id]=a[t][x].d^1;
		if(vis[a[t][x].to])return;
		vis[a[t][x].to]=1;
		x=a[t][x].to;t^=1;
	}
}
signed main(){
	n=in;m=in;tot=m+1;
	for(int i=1;i<=m;i++){
		int a=in;int b=in;int c=in;
		add(a,b,i,c-1);
	}
	for(int i=0;i<n;i++)if(!vis[i])work(i);
	for(int i=tot;i>0;i--){
		if(depend[i])ans[i]=ans[depend[i]]^rev[i];
	}
	for(int i=1;i<=m;i++)cout<<ans[i];
	return 0;
} 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值