「GDKOI2021普及组Day1」简要题解

T1:地图

题目大意:给出一个 n × n n\times n n×n的01矩阵,其中 a 1 , 1 = 1 a_{1,1}=1 a1,1=1 a 1 , i a_{1,i} a1,i表示第 i i i列和第 n n n列所有元素的异或值(不包括 a 1 , i a_{1,i} a1,i), a i , 1 a_{i,1} ai,1表示第 i i i行和第 n n n行所有元素的异或值(不包括 a i , 1 a_{i,1} ai,1),现在更改这个矩阵的里的一个数,问更改的数是哪个。 n ≤ 2000 n\le 2000 n2000

题解:

如果这个矩阵有元素进行改动,那么可以知道 a 1 , i a_{1,i} a1,i a i , 1 a_{i,1} ai,1可能会有元素有问题

那么可以将这个矩阵的有问题的 a 1 , i a_{1,i} a1,i a i , 1 a_{i,1} ai,1找到,然后分类讨论

  1. 行和列上都没有问题:那么显然更改了 a 1 , 1 a_{1,1} a1,1
  2. 行和列上都只有一个元素有问题:行和列的交点就是更改的元素
  3. 行上全部有问题、列上只有一个有问题(反之亦然):行上都有问题说明在更改的数在第 n n n行,那么对应的哪列有问题更改的数就在哪列
  4. 行和列上的元素本身有问题
  5. 只有行有问题(或只有列):那么就是 a n , 1 a_{n,1} an,1(或者 a 1 , n a_{1,n} a1,n
  6. 行和列都是全部有问题: a n , n a_{n,n} an,n

Code

#include<cstdio>
#define N 2005
using namespace std;
int n,num,rnum,cnum,a[N][N],c[N],r[N],err[N*N][3],err_r[N],err_c[N];
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
	{
		for (int j=1;j<=n;++j)
		{
			scanf("%d",&a[i][j]);
			if (j!=1) c[i]^=a[i][j];
			if (i!=1) r[j]^=a[i][j];
		}
	}
	c[n]^=a[n][1];
	r[n]^=a[1][n];
	if (a[1][1]==1)
	{
		printf("1 1\n");
		return 0;
	}
	for (int i=2;i<n;++i)
	{
		if (c[i]^c[n]!=a[i][1]) ++cnum,err_c[cnum]=i,++num,err[num][1]=i,err[num][2]=1;
		if (r[i]^r[n]!=a[1][i]) ++rnum,err_r[rnum]=i,++num,err[num][1]=1,err[num][2]=i;
	}
	if (num==1)
	{
		printf("%d %d\n",err[1][1],err[1][2]);
		return 0;
	}
	if (num==2)
	{
		if (rnum==1&&cnum==1)
		{
			printf("%d %d\n",err_c[cnum],err_r[rnum]);
			return 0;
		}
	}
	if (num==n-2)
	{
		if (cnum==n-2) printf("%d 1\n",n);
		else printf("1 %d\n",n);
		return 0;
	}
	if (num==n-1)
	{
		if (cnum==1) printf("%d %d\n",err_c[cnum],n);
		else printf("%d %d\n",n,err_r[rnum]);
	}
	if (num==2*(n-2))
	{
		printf("%d %d\n",n,n);
		return 0;
	}
	return 0;
}

T2:灌水

题目大意:有一宽度为 n n n的水槽,水槽两边无限高,每个单位长度都有一定的高度,水会往左右扩散至第一个比自己高度高的位置。 q q q次操作,每次将第 x x x位的水灌到 y y y(操作之间没有影响),问水会扩散到多少个格子。 n , q ≤ 2 × 1 0 5 n,q\le2\times10^5 n,q2×105

题解:

想到一种显然的暴力就是从当前位置往左右枚举,直到找到一个比自己高的位置,时间复杂度 O ( q n ) O(qn) O(qn)

考虑优化,发现可以不用枚举,可以二分,同时维护一下区间最大值即可。

维护区间最大值建议使用ST表,时间复杂度 O ( 1 ) O(1) O(1)查询最大值,总的时间复杂度 O ( q log ⁡ n ) O(q\log n) O(qlogn)

如果用线段树时间复杂度会到 O ( q log ⁡ 2 n ) O(q\log^2n) O(qlog2n),会被卡

Code

#include<cmath> 
#include<cstdio>
#include<algorithm>
#define inf 2147483647
#define N 500005
#define ll long long 
using namespace std;
int n,q,x,l,r,mid,res,L,R;
ll y,a[N],sum[N],f[N][20],lg[N];
int query(int l,int r)
{
	int x=lg[r-l+1];
	return max(f[l][x],f[r-(1<<x)+1][x]);	
} 
int main()
{
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;++i)
		scanf("%lld",&a[i]),f[i][0]=a[i];
	for (int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
	for (int j=1;j<=18;++j)
		for (int i=1;i<=n;++i)
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	for (int i=1;i<=n;++i)
		sum[i]=sum[i-1]+a[i];
	a[0]=a[n+1]=inf;
	for (int i=1;i<=q;++i)
	{
		scanf("%d%lld",&x,&y);
		l=1;r=x-1;
		res=0;
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (query(mid,r)>=y) res=mid,l=mid+1;
			else r=mid-1;
		}
		L=res;
		l=x+1;r=n;
		res=n+1;
		while (l<=r)
		{	
			mid=(l+r)>>1;
			if (query(l,mid)>=y) res=mid,r=mid-1;
			else l=mid+1;
		}
		R=res;
		printf("%lld\n",(R-L-1)*y-(sum[R-1]-sum[L]));
	}
	return 0;
}

T3:配对

题目大意:给出 n n n个数( n n n是偶数),现在要删去一些数,让剩下的数两两配对后和在 [ l , r ] [l,r] [l,r],问最少删去多少个数(必须删去偶数个,如果全删完了也满足条件,可以不删)。 n ≤ 1 0 6 n\le 10^6 n106

题解:

先将序列排序

发现如果 ( a , c ) ( b , d ) (a,c)(b,d) (a,c)(b,d)是合法的,那么 ( a , d ) ( b , c ) (a,d)(b,c) (a,d)(b,c)也是合法的( a ≤ b ≤ c ≤ d a\le b\le c\le d abcd

略证:

因为 ( a , c ) (a,c) (a,c)合法,那么 l ≤ a + c ≤ r l\le a+c\le r la+cr

同理 l ≤ b + d ≤ r l\le b+d\le r lb+dr

那么 l ≤ a + c ≤ a + d ≤ b + d ≤ r l\le a+c\le a+d\le b+d\le r la+ca+db+dr

l ≤ a + c ≤ b + c ≤ b + d ≤ r l\le a+c\le b+c\le b+d\le r la+cb+cb+dr

所以 ( a , d ) ( b , c ) (a,d)(b,c) (a,d)(b,c)合法

那么可以思考一种贪心

用两个指针分别指到头尾(设头元素是 x x x,尾元素是 y y y

如果 x + y > r x+y>r x+y>r,那么 y y y已经无法造成合法贡献了,可以直接删除

同理如果 x + y < l x+y<l x+y<l x x x也不会造成任何贡献

如果 l ≤ x + y ≤ r l\le x+y\le r lx+yr,那么就可以把 x , y x,y x,y配对,然后往下走

最后注意判断如果删掉了奇数那就再删一个

Code

#include<cstdio>
#include<algorithm>
#define N 1000005
using namespace std;
int n,l,r,ans,a[N];
int main()
{
	scanf("%d%d%d",&n,&l,&r);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	int i=1,j=n;
	while (i<j)
	{
		if (a[i]+a[j]>r) --j,++ans;
		else if (a[i]+a[j]<l) ++i,++ans;
		else if (a[i]+a[j]>=l&&a[i]+a[j]<=r) ++i,--j;
	}
	if (i==j) ++ans;
	printf("%d\n",ans);
	return 0;
}

T4:旅行

题目大意:给出一张 n n n m m m边的图, q q q次询问,每次询问要求从 x x x开始,只能经过边权 ≤ w \le w w的边,问最多能到达多少个点。 n , q ≤ 2 × 1 0 5 , m ≤ 4 × 1 0 5 n,q\le2\times10^5,m\le4\times10^5 n,q2×105,m4×105

题解:

其实运用了一个显而易见的结论将题目简化了。

由于询问之间互相独立,考虑离线,发现会走的边只会在最小生成树上,那么将询问的 w w w和每条边的边权从小到大排序,每次询问将所有 ≤ w \le w w的边加入,维护并查集的同时记录连通块大小即可

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
struct node
{
	int from,to,val;
}a[N<<1];
struct ques
{
	int id,val,pos;
}c[N];
int n,m,q,x,y,z,f[N],size[N],ans[N];
bool cmpm(node x,node y) {return x.val<y.val;}
bool cmpq(ques x,ques y) {return x.val<y.val;}
int find(int x)
{
	if (f[x]!=x) f[x]=find(f[x]);
	return f[x];
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i)
		f[i]=i,size[i]=1;
	for (int i=1;i<=m;++i)
		scanf("%d%d%d",&a[i].from,&a[i].to,&a[i].val);
	sort(a+1,a+m+1,cmpm);
	scanf("%d",&q);
	for (int i=1;i<=q;++i)
		scanf("%d%d",&c[i].pos,&c[i].val),c[i].id=i;
	sort(c+1,c+q+1,cmpq);
	int i=1,j=1;
	while (j<=q)
	{
		while (a[i].val<=c[j].val&&i<=m)
		{
			int xx=find(a[i].from),yy=find(a[i].to);
			if (xx!=yy)
			{
				f[xx]=yy;
				size[yy]+=size[xx];
			}
			++i;
		}
		int xx=find(c[j].pos);
		ans[c[j].id]=size[xx];
		++j;
	}
	for (int i=1;i<=q;++i)
		printf("%d\n",ans[i]);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值