2019.11.06【NOIP提高组】模拟 A 组(tarjan求点双、线段树优化连边)

T1:用tarjan求一遍点双,然后判断每个点双内的点数是否等于边数,是就把这些边加入答案。

具体求点双的参见“tarjan”有关算法总结。

 

T2:这是一道大水题,但是我却没有想出来。

首先将m-n*a[1],然后发现如果在第i个位置加A,那么对最终答案的影响就是(n-i+1)*A,如果减B就是-B*(n-i+1)。现在的目标就是求出那些位置加A,那些位置减B。

设加A位置的系数和为x,减B位置的系数和为y,那么:

1、Ax-By=m'

2、x+y=n*(n-1)/2

解出方程,在暴力拆分x或y即可。

 

总结:还是要多列性质和方向。

 

T3:首先暴力连边+拓扑序求解的方法很容易想到。

接着我们发现每次连边都是若干个点向若干个区间连,这时可以用线段树来优化连边。

首先对于一次权值大的点连向一个权值小的区间,我们可以新建一个节点,权值大的点连一条1边到这个新点,新点再连一条0边到权值小的区间。注意此时一个区间可以拆分成线段树上的log个区间,这样就保证了复杂度是nlogn的。

最后为了保证连边的合法性,我们对于每个叶节点,要将它的所有祖先向它连一条0边。这样就保证了构出来的拓扑图是正确的。

 

总结:这种线段树优化连边的方式在一个点连向一个区间是可以使用。而它保证正确性的精髓就是每一个叶子节点的所有祖先都要向它连边。

 

贴一下代码,方便理解:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 100010
#define MAXM 8000010
#define MAXP 3000010

struct map
{
	int x;
	int y;
	int l;
};
map way[MAXM];
int first[MAXP],nxt[MAXM],num[MAXN],a[MAXP],d[MAXP],cnt[MAXP],fa[MAXP],bz[MAXP],n,ne,m,q;
int x,y,s;
int dg(int l,int r,int t)
{
	if(t>ne)ne=t;
	if(l==r)num[l]=t;
	else
	{
		fa[t*2]=t;dg(l,(l+r)/2,t*2);
		fa[t*2+1]=t;dg((l+r)/2+1,r,t*2+1);
	}
}
int link(int l,int r,int t)
{
	if(x<=l&&r<=y){m++;way[m].x=ne;way[m].y=t;way[m].l=0;}
	else
	{
		if(l==r)return 0;
		if(l<=x&&x<=(l+r)/2||l<=y&&y<=(l+r)/2||x<=l&&(l+r)/2<=y)link(l,(l+r)/2,t*2);
		if((l+r)/2+1<=x&&x<=r||(l+r)/2+1<=y&&y<=r||x<=(l+r)/2+1&&r<=y)
			link((l+r)/2+1,r,t*2+1);
	}
}
int main()
{
//freopen("web.in","r",stdin);
//freopen("web.out","w",stdout);
int i,j,l,r,k,p,v,w,h,t;
scanf("%d %d %d",&n,&s,&q);
dg(1,n,1);
for(i=1;i<=n;i++)
{
	j=num[i];
	while(fa[j]!=0)
	{
		m++;way[m].x=fa[j];way[m].y=num[i];way[m].l=0;
		j=fa[j];
	}
}
while(s>=1)
{
	scanf("%d %d",&p,&v);
	a[num[p]]=v;bz[num[p]]=1;s--;
}
while(q>=1)
{
	scanf("%d %d %d",&l,&r,&k);
	ne++;h=l;
	while(k>=1)
	{
		scanf("%d",&w);
		m++;way[m].x=num[w];way[m].y=ne;way[m].l=1;
		x=h;y=w-1;
		if(x<=y)link(1,n,1);
		h=w+1;
		k--;
	}
	x=h;y=r;
	if(x<=y)link(1,n,1);
	q--;
}
for(i=m;i>=1;i--)nxt[i]=first[way[i].x],first[way[i].x]=i;
for(i=1;i<=m;i++)cnt[way[i].y]++;
h=0;t=0;
for(i=1;i<=ne;i++)
	if(bz[i]==0)a[i]=1000000000;
for(i=1;i<=ne;i++)
	if(cnt[i]==0){t++;d[t]=i;}
while(h<t)
{
	h++;
	for(i=first[d[h]];i>=1&&i<=m;i=nxt[i])
	{
		if(bz[way[i].y]==0)
			if(a[d[h]]-way[i].l<a[way[i].y])a[way[i].y]=a[d[h]]-way[i].l;
		cnt[way[i].y]--;
		if(cnt[way[i].y]==0){t++;d[t]=way[i].y;}
	}
}
printf("Possible\n");
for(i=1;i<=n;i++)printf("%d ",a[num[i]]);
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值