南海云课堂 · noip一班 · 测试(2023-2-23)·(钻石,区间,宝物,变色小球)

posted on 2023-03-08 05:15:19 | under 题集 | source

第一题 钻石

题意

有一个R行C列的网格,我们用(i,j)表示第i行第j列的格子。

有k个钻石,第i个钻石所在的格子是(r[i],c[i]),价值是v[i]。

你要从左上角格子(1,1)出发,每一步可以向下或向右走一格,最终要到达右下角格子(R,C)。

每进入一个有钻石的格子,你就可以收集到该钻石的价值。

但是有一个规定是:同一行的所有格子当中,你最多只能收集3个钻石。

比如你可以在同一行中到达4个或者更多有钻石的格子,但是你最多只能收集3个钻石。

求能收集到的钻石的最大价值。

出发点格子(1,1)和终点格子(R,C)如果有钻石,也是按照上面的规则。

输入格式

第一行,三个整数R,C,k。1<=R,C<=3000, 1<=K<=min(200000,R*C)。

接下来有k行,第i行有3个整数: r[i],c[i],v[i]。1<=r[i]<=R, 1<=c[i]<=C, 1<=v[i]<=10^9。

同一个格子最多只有一个钻石。

输出格式

一个整数。

输入/输出例子1

输入:

2 2 3

1 1 3

2 1 4

1 2 5

输出:

8

输入/输出例子2

输入:

2 5 5

1 1 3

2 4 20

1 2 1

1 3 4

1 4 2

输出:

29

输入/输出例子3

输入:

4 5 10

2 5 12

1 5 12

2 3 15

1 2 20

1 1 28

2 4 26

3 2 27

4 5 21

3 5 10

1 3 10

输出:

142

思路

这道题用 DP 做。

dp[i][j][k] 表示在第 i 行,第 j 列,这一行取了 k 个钻石时的最大价值。

再定义 ma[i][j] 数组,表示在第 i 行,第 j 列时的最大价值,当然也可以不这样做。

然后是状态转移方程,(i,j) 可以由 (i-1,j) 和 (i,j-1) 得到,这里分为取和不取两种情况讨论:

不取时:dp[i][j][k]=max(dp[i][j-1][k],ma[i-1][j]);

取时(注意 k 不能为 0):dp[i][j][k]=max(dp[i][j-1][k-1],ma[i-1][j])+v[i][j];

对两种情况取最大值即可,接着为 ma 数组赋值:ma[i][j]=max(dp[i][j][k],ma[i][j]);

最后输出 ma[r][c] 即可。

#include<bits/stdc++.h>
using namespace std;
long long r,c,k,x,y,v[3005][3005],dp[3005][3005][4],ma[3005][3005]; 
int main()
{
	cin>>r>>c>>k;
	for(int i=1; i<=k;i++)
	{
		scanf("%lld%lld",&x,&y);
		scanf("%lld",&v[x][y]);
	}
	for(int i=1; i<=r;i++)
	 for(int j=1; j<=c;j++)
	  for(int p=0; p<=3;p++)
	  {
	  	//不取 
	  	dp[i][j][p]=max(dp[i][j-1][p],ma[i-1][j]);
	  	//取
		if(v[i][j]!=0&&p!=0) 
		{
			dp[i][j][p]=max(dp[i][j][p],max(dp[i][j-1][p-1],ma[i-1][j])+v[i][j]);
		}
	  	ma[i][j]=max(ma[i][j],dp[i][j][p]);
	  } 
	cout<<ma[r][c];
	return 0;
 }

第二题 区间

题意

给出n个数a[1...n],有m次操作,第i次操作的格式是:T[i],X[i],Y[i]。

1、若T[i]=1, 表示的意义是执行a[X[i]] = a[X[i]] ⊕Y[i]。

2、若T[i]=2, 要输出a[X[i]]⊕a[X[i]+1]⊕a[X[i]+2]......⊕a[Y[i]]的结果。

其中⊕表示二进制数位操作的异或运算。

输入格式

第一行,n和m。1<=n,m<=300000。

第二行,共n个整数,第i个整数是a[i], 0<=a[i]<=2^30。

接下来m行,每行格式如题目所述。若T[i]=1,则1<=X[i]<=n, 0<=Y[i]<=2^30;

若T[i]=2,则1<=X[i]<=Y[i]<=n。

输出格式

对于查询操作,输出结果。

输入/输出例子1

输入:

3 4

1 2 3

2 1 3

2 2 3

1 2 3

2 2 3

输出:

0

1

2

输入/输出例子2

输入:

10 10

0 5 3 4 7 0 0 0 1 0

1 10 7

2 8 9

2 3 6

2 1 6

2 1 10

1 9 4

1 6 1

1 6 3

1 1 7

2 3 5

输出:

1

0

5

3

0

思路

首先要知道异或运算的性质:

  1. abc=(ab)c=a(bc)

因为它满足结合律,所以可以用分块思想,也就是线段树、树状数组这一类的结构。

然后是它的逆运算,写树状数组时要用到:

  1. 0^a=a

  2. a^a=0

所以 (abcd)(ab)=cd 。

这样它就变成了一道模板题。由于它只需单点修改,因此我是用树状数组做的。

#include<bits/stdc++.h>
using namespace std;
int n,m,t,x,y,a[300005];
int lowbit(int k) {return k&-k;}
void update(int k,int p)
{
    while(k<=n)
    {
        a[k]^=p;
        k+=lowbit(k);
    }
}
int sum(int k)
{
    int ans=0;
    while(k>0)
    {
        ans^=a[k];
        k-=lowbit(k);
    }
    return ans;
}
int main(){
    cin>>n>>m;
    for(int i=1; i<=n;i++)
    {
        scanf("%d",&t);
        update(i,t);
    }
    for(int i=1; i<=m;i++)
    {
        scanf("%d%d%d",&t,&x,&y);
        if(t==1) update(x,y);
        else printf("%d\n",sum(y)^sum(x-1));
    }
    return 0;
}

第三题 宝物(所有数据)

题意

有一个R行C列的网格。有n件宝物,第i件宝物在第r[i]行第c[i]列的格子。

你有一个磁铁,你可以把磁铁放到网格的某个格子,磁铁会把它所在行和所在列的宝物全部吸收过来。

磁铁应该放到哪个格子,才能使得吸到的宝物的数量最大,输出该最大值。

注意:磁铁放到有宝物的格子也是允许的。

输入格式

第一行,3个整数,R,C,n。1<=R,C<=300000, 1<=n<=min(300000,R*C)。

接下来有n行,每行两个整数,第i行是r[i],c[i]。1<=r[i]<=R, 1<=c[i]<=C。

一个格子不会有多个宝物。

输出格式

一个整数。

输入/输出例子1

输入:

2 3 3

2 2

1 1

1 3

输出:

3

输入/输出例子2

输入:

3 3 4

3 3

3 1

1 1

1 2

输出:

3

输入/输出例子3

输入:

5 5 10

2 5

4 3

2 3

5 5

2 2

5 4

5 3

5 1

3 5

1 4

输出:

6

思路

这道题的难点在于:需要判断磁铁在不在有宝物的格子上,不然会多算一个。

但我们只用对行、列的最大值进行讨论。

因为:最大 - 1 >= 第二大

然后对最大的行列进行枚举,判断交点上有没有宝物即可。

看似是 O(n^2) 的复杂度?

可是只有 n 个宝物。

最坏情况下,枚举 n+1 次一定能找到空格子。

最后是实现:

用下标计数记录每行、每列有几个宝物, 找出最大值。

用 map 记录当前点有没有宝物。

#include<bits/stdc++.h>
using namespace std;
int r,c,n,a,b,x[300005],y[300005],xx,yy,mx[300005],my[300005],cmx,cmy,ans;
map<long long,long long>mp;
long long zh(int x,int y) {return (x-1)*r+y;}
int main(){
    cin>>r>>c>>n;
    for(int i=1; i<=n;i++)
    {
    	scanf("%d%d",&a,&b);
    	mp[zh(a,b)]=1;
    	x[a]++;y[b]++;
    	xx=max(xx,x[a]);
    	yy=max(yy,y[b]);
    }
    for(int i=1; i<=r;i++) if(x[i]==xx) mx[++cmx]=i;
    for(int i=1; i<=c;i++) if(y[i]==yy) my[++cmy]=i;
    ans=xx+yy-1;
    for(int i=1; i<=cmx;i++)
    {
    	for(int j=1; j<=cmy;j++)
    	{
    		if(!mp[zh(mx[i],my[j])]) 
    		{
    			cout<<ans+1;
    			return 0;
    		}
    	}
    }
    cout<<ans;
    return 0;
}

第五题 变色小球

题意

小C有N个白色小球,他们之间有些是相互连接的,编号为1,2,……,N。

小C可以通过操作改变小球的颜色,但每次操作都会使相连这两个小球同时变色(白色变黑色,黑色变白色),他有m次操作可以选择,请问小C最多可以有多少个黑色小球?

输入格式

第一行输入两个整数N,m,分别表示小C有N个小球,m次操作。

接下来有m行,每行有两个整数x,y,分别表示这次操作相连的两个小球的编号。

【数据范围】

50%的数据,1<=N、m<=20;

100%的数据,1<=N、m<=10^6;

输出格式

输出一个正整数,即小C操作完后,最多可以有的黑色小球的数量。

输入/输出例子1

输入:

3 3

1 2

2 3

1 3

输出:

2

输入/输出例子2

输入:

6 3

1 3

2 3

6 5

输出:

4

思路

我们可以把相连的小球理解为:连接两个点的边。

操作完后我们可以得到若干个连通块,然后研究这些连通块内点的关系。

结论 1:对同一连通块中的一对点进行变色操作,不会影响其它点的颜色。

证:

可以把这对点理解为 A1 和 An,则路径上的点为 A1、A2 ...... An。

先改变(A1,A2),再改变(A2,A3),然后我们会发现 A2 改变了两次。最后一次操作是(An-1,An),因此 A1 和 An 被改变一次,其它点被改变两次(等于没变)。

结论 2:偶数点的连通块至多能改变 n 个点变为黑点,奇数点的连通块至多能改变 n-1 个点变为黑点,其中 n 为联通块有几个点。

对于偶数点的情况,每次选取一对白点改为黑点,由结论 1 可知不会改变其它点。

对于奇数点的情况,首先对其中 n-1 个点进行修改,n-1 为偶数因此能全变为偶数。

剩下 1 个白点,分情况讨论:

  1. 两个黑点,还不如不改。
  2. 一黑一白,操作完后黑变白 白变黑,黑白点数量不变。

因此只能改变 n-1 个点变为黑点。

那我们就用并查集对每个球建立关系,并统计每个连通块的大小,非常简单。

#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,fa[1000005],size[1000005],f[1000005],ans;
int find(int k)
{
	if(fa[k]==k) return k;
	return fa[k]=find(fa[k]);
}
void add(int x,int y)
{
	int xx=find(x),yy=find(y);
	if(xx!=yy)
	{
		fa[xx]=yy;
		size[yy]+=size[xx];
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1; i<=n;i++) fa[i]=i,size[i]=1;
	for(int i=1; i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1; i<=n;i++)
	{
		int k=find(i);
		if(f[k]==1) continue;
		f[k]=1;
		ans+=size[k]/2*2;
	}
	cout<<ans;
	return 0;
 }
posted @ 2026-01-14 18:10  Zwi  阅读(5)  评论(0)    收藏  举报