纪中暑假集训 2020.07.21【NOIP提高组】模拟 反思+题解

这次比赛打了第2名,130分,主要是T3数据太水,被我水过去了,运气好啊……

T1:【NOIP2015模拟10.22】矩形

Description

给定一个由数字(0-9)构成的字符串s。我们可以由此定义出size(s) * size(s) 大
小的矩阵b,其中b[i][j] = s[i] * s[j];请问在这个矩阵b中,有多少子矩形满足其中的b[i][j]的和为另一个给定的数字a。

Input

第一行一个整数a。
第二行字符串s。

Output

一个整数表示满足条件的子矩形数。

Sample Input

10
12345

Sample Output

6

【样例解释】

b 矩阵为:
01 02 03 04 05
02 04 06 08 10
03 06 09 12 15
04 08 12 16 20
05 10 15 20 25
和为 10 的子矩形有:
一、01 02 03 04
二、
01
02
03
04
三、04 06
四、
04
06
五、10
六、10
以上共六个。

Data Constraint

对 10%的输入数据:size(s)≤10
对30%的输入数据:size(s)≤100
对100%的输入数据:0 ≤a≤1000000000,size(s)≤4000

反思&题解

比赛思路: 二维前缀和 n 4 n^4 暴力
正解思路: 先用桶记录前缀和的个数,之后因为a是由一段行的和乘上一段列乘积,也就是说这两段都是a的约数,之后我们枚举每一段行的和,那么我们就需要找到符合此规律所对应的列的和,之后累加一下就行了
有一个特判,就是 a = 0 a=0 时,当行的和为0时,列的和可以取任何数
反思: 要熟悉每一种题型以及所对应的特性,这样子更容易做出题目

CODE

#include<bits/stdc++.h>
using namespace std;
int a,len,sum[4005],num[36005];
long long ans;
char s[4005];
int main()
{
	cin>>a;
	cin>>s+1;
	len=strlen(s+1);
	int i,j;
	for (i=1;i<=len;i++)
		sum[i]=sum[i-1]+(s[i]-'0');
	for (i=1;i<=len;i++)
	{
		for (j=i;j<=len;j++)
			num[sum[j]-sum[i-1]]++;
	}
	if (a)
	{
		for (i=1;i<=len;i++)
		{
			for (j=i;j<=len;j++)
			{
				if (sum[j]-sum[i-1]!=0 && a%(sum[j]-sum[i-1])==0 && a/(sum[j]-sum[i-1])<=36000) ans+=num[a/(sum[j]-sum[i-1])];
			}
		}
	}
	else
	{
		for (i=1;i<=len;i++)
		{
			for (j=i;j<=len;j++)
			{
				if (sum[j]-sum[i-1]==0) ans+=len*(len+1)/2;
				else ans+=num[0];
			}
		}	
	}
	printf("%lld\n",ans);		
	return 0;
}

T2:【NOIP2015模拟10.22】最大子矩阵

Description

我们将矩阵A中位于第i行第j列的元素记作A[i,j]。一个矩阵A是酷的仅当它满足下面的条件:
A[1,1]+A[r,s]<=A[1,s]+Ar,1
其中r为矩阵A的行数,s为矩阵A的列数。
进一步,如果一个矩阵是非常酷的仅当它的每一个至少包含两行两列子矩阵都是酷的。
你的任务是,求出一个矩阵A中的一个非常酷的子矩阵B,使得B包含最多元素。

Input

第一行包含两个整数R,S(2<=R,S<=1000),代表矩阵的行数与列数。
接下来R行每行包括S个整数,代表矩阵中的元素,矩阵中元素的绝对值不大于1000000。

Output

一行一个整数,代表子矩阵B的元素总数。如果没有一个非常酷的子矩阵,输出0。

Sample Input

输入1:
3 3
1 4 10
5 2 6
11 1 3
输入2:
3 3
1 3 1
2 1 2
1 1 1
输入3:
5 6
1 1 4 0 3 3
4 4 9 7 11 13
-3 -1 4 2 8 11
1 5 9 5 9 10
4 8 10 5 8 8

Sample Output

输出1:
9
输出2:
4
输出3:
15

【样例3解释】

在第三个样例中,子矩阵B的左上角为A[3,2],右下角为A[5,6]。

Data Constraint

对于60%的数据,满足R,S<=350。
对于100%的数据,满足2<=R,S<=1000,矩阵中元素的绝对值不大于1000000。

反思&题解

比赛思路: 懵……
正解思路: 因为题目要求时说一个矩阵时非常酷的时候要当每一个 2 2 2*2 的子矩阵都是酷的才成立,所以我们设 b [ i ] [ j ] b[i][j] 表示从 i , j i,j 这个点向上找最长的酷 2 x 2 2x*2 的酷序列能有多长,注意虽然是要两行一起处理但是其表示的只有j这一行的结果
处理出来之后我们就用一个单调栈来维护答案,首先这个可以加进栈的时候时当栈的长度不为0时且栈尾比当前要进栈的大,之后我们就要把所有大于他的栈尾全部弹掉,在计算当前的面积根答案取max
最后每次处理完一行要把栈里的东西全部弹掉
反思: 数据结构方面的知识也要加强

CODE

#include<bits/stdc++.h>
using namespace std;
int a[1005][1005],b[1005][1005],r,s,ans,zhan[1005];
int main()
{
	scanf("%d%d",&r,&s);
	int i,j;
	for (i=1;i<=r;i++)
	{
		for (j=1;j<=s;j++)
		{
			scanf("%d",&a[i][j]);
			b[i][j]=1;
		}
	}
	for (j=2;j<=s;j++)
	{
		for (i=2;i<=r;i++)
			if (a[i-1][j-1]+a[i][j]<=a[i][j-1]+a[i-1][j]) b[i][j]=b[i-1][j]+1;
	}
	zhan[0]=1;
	for (i=2;i<=r;i++)
	{
		int tot=0;
		for (j=2;j<=s;j++)
		{
			while (tot && b[i][j]<=b[i][zhan[tot]])
			{
				ans=max(ans,b[i][zhan[tot]]*(j-zhan[tot-1]));
				tot--;	
			}
			zhan[++tot]=j;
		}
		while (tot)
		{
			ans=max(ans,b[i][zhan[tot]]*(s+1-zhan[tot-1]));
			tot--;
		}
	}
	printf("%d\n",ans);
	return 0;
}

T3:【NOIP2015模拟10.22】最小代价

Description

给出一幅由n个点m条边构成的无向带权图。
其中有些点是黑点,其他点是白点。
现在每个白点都要与他距离最近的黑点通过最短路连接(如果有很多个黑点,可以选取其中任意一个),我们想要使得花费的代价最小。请问这个最小代价是多少?
注意:最后选出的边保证每个白点到离它最近的黑点的距离仍然等于原图中的最短距离。

Input

第一行两个整数n,m;
第二行n 个整数,0表示白点,1 表示黑点;
接下来m 行,每行三个整数x,y,z,表示一条连接x和y 点,权值为z 的边。

Output

如果无解,输出impossible;
否则,输出最小代价。

Sample Input

5 7
0 1 0 1 0
1 2 11
1 3 1
1 5 17
2 3 1
3 5 18
4 5 3
2 4 5

Sample Output

5

【样例解释】

选 2、4、6三条边

Data Constraint

对30%的输入数据: 1≤n≤10, 1≤m≤20;
对100%的输入数据:1≤n≤100000,1≤m≤200000,1≤z≤1000000000

反思&题解

比赛思路(因数据太水所以过了): 因为从宏观的角度来看每个最短路并没有啥联系,那么为了把他们联系起来(主要因为这样可能好做一点),就可以设一个0点连向每一个黑点,边权为0,之后跑一遍从SPFA,之后把每个点最短路所在的边放进一个数组里,之后跑一遍最小生成树就行了
正解思路: 首先我们看一下这个数据:
5 5
1 0 0 0 0
1 2 2
2 3 2
3 4 2
1 5 5
5 4 1
正确答案应该是10,但是水法程序跑出来的是7,因为在跑最小生成树的时候会舍去1到5的这一条最短路,所以应该在最小生成树中加一些判断(反正我是没打)
然而有一个code量更短,时间更快,更易理解的正解
首先建点连边跑SPFA的地方的大纲不变,只需要在SPFA中记录一下一个点的最短路最后那条边,之后如果有更新就将答案减去这个值再加上新的边的权值,在修改一下记录的行了
反思: 所以说有时候运气真的很重要注意一下方法的合理性,不然运气没那么好就挂了……

CODE

水法:

#include<bits/stdc++.h>
using namespace std;
struct arr
{
	long long to,next,w; 
}edge[600005];
struct arr1
{
	long long uu,vv,ww;
}a[600005];
queue<long long>q;
long long dist[100005],fa[100005],head[600005],ans,n,m,tot,cnt;
bool bz[100005];
void spfa()
{
	q.push(0);
	dist[0]=0;
	while (!q.empty())
	{
		long long now=q.front();
		q.pop();
		bz[now]=false;
		long long i;
		for (i=head[now];i;i=edge[i].next)
		{
			long long v=edge[i].to;
			if (dist[now]+edge[i].w<dist[v])
			{
				dist[v]=dist[now]+edge[i].w;
				if (!bz[v])
				{
					bz[v]=true;
					q.push(v);	
				}	
			}
		}
	}
}
void check(int now)
{
	long long i;
	for (i=head[now];i;i=edge[i].next)
	{
		long long v=edge[i].to;
		if (dist[now]+edge[i].w==dist[v])
		{
			check(v);
			a[++tot].uu=now;
			a[tot].vv=v;
			a[tot].ww=edge[i].w;
		}
	}
}
long long find(long long k)
{
	if (fa[k]==k) return k;
	else
	{
		fa[k]=find(fa[k]);
		return fa[k];
	}
}
bool cmp(arr1 xx,arr1 yy)
{
	return xx.ww<yy.ww;
}
void add(int u,int v,int len)
{
	edge[++cnt].to=v;
	edge[cnt].w=len;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	long long i,k,x,y,z;
	for (i=1;i<=n;i++)
	{
		scanf("%lld",&k);
		if (k) add(0,i,0);
	}
	for (i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	memset(dist,0x3f3f3f3f,sizeof(dist));
	memset(bz,false,sizeof(bz));
	spfa();
	check(0);
	sort(a+1,a+1+n,cmp);
	for (i=0;i<=n;i++)
		fa[i]=i;
	ans=0;
	for (i=1;i<=tot;i++)
	{
		long long t1=find(a[i].uu),t2=find(a[i].vv);
		if (t1!=t2)
		{
			fa[t1]=t2;
			ans+=a[i].ww;
		}
	}
	for (i=1;i<=n;i++)
		printf("%lld\n",dist[i]);
	if (!ans) printf("impossible\n");
	else printf("%lld\n",ans); 
	return 0;
}

正解:

#include<bits/stdc++.h>
using namespace std;
struct arr
{
	long long to,next,w; 
}edge[600005];
queue<long long>q;
long long dist[100005][2],head[600005],ans,n,m,tot,cnt;
bool bz[100005];
void spfa()
{
	q.push(0);
	dist[0][0]=0;
	dist[0][1]=0;
	while (!q.empty())
	{
		long long now=q.front();
		q.pop();
		bz[now]=false;
		long long i;
		for (i=head[now];i;i=edge[i].next)
		{
			long long v=edge[i].to;
			if (dist[now][0]+edge[i].w<dist[v][0])
			{
				ans=ans-dist[v][1]+edge[i].w;
				dist[v][1]=edge[i].w;
				dist[v][0]=dist[now][0]+edge[i].w;
				if (!bz[v])
				{
					bz[v]=true;
					q.push(v);	
				}
			}
		}
	}
}
void add(int u,int v,int len)
{
	edge[++cnt].to=v;
	edge[cnt].w=len;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	long long i,k,x,y,z;
	for (i=1;i<=n;i++)
	{
		scanf("%lld",&k);
		if (k) add(0,i,0);
	}
	for (i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	for (i=1;i<=n;i++)
	{
		dist[i][0]=1234567891011;
		dist[i][1]=0;
	}
	memset(bz,false,sizeof(bz));
	ans=0;
	spfa();
	for (i=1;i<=n;i++)
	{
		if (dist[i][0]==1234567891011)
		{
			printf("impossible\n");
			return 0;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

T4:【NOIP2015模拟10.22】WTF交换

Description

假定给出一个包含N个整数的数组A,包含N+1个整数的数组ID,与整数R。其中ID数组中的整数均在区间[1,N-1]中。
用下面的算法对A进行Warshall-Turing-Fourier变换(WTF):
sum = 0
for i = 1 to N
index = min{ ID[i], ID[i+1] }
sum = sum + A[index]
将数组A往右循环移动R位
将数组A内所有的数取相反数
for i = 1 to N
index = max{ ID[i], ID[i+1] }
index = index + 1
sum = sum + A[index]
将数组A往右循环移动R位
给出数组A以及整数R,但没有给出数组ID。在对数组A进行了WTF算法后,变量sum的可能出现的最大值数多少?

Input

第一行包含两个整数N与R。
第二行包含N个整数,代表A[1]到A[N]的值。

Output

第一行输出变量sum可能出现的最大值。
第二行输出此时的ID数组,包含N+1个整数。必须满足ID数组中每一个数均在区间[1,N-1]中。若有多个满足的ID数组,输出任意一组。
如果第一行是正确的(不管有没有输出第二行),你能得到该测试点50%的得分。

Sample Input

输入1:
5 3
1 -1 1 -1 1
输入2:
6 5
2 5 4 1 3 5

Sample Output

输出1:
10
1 1 1 2 2 3
输出2:
16
3 2 1 1 5 4 1

Data Constraint

对于20%的数据,N<=7。
对于60%的数据,N<=300。
对于100%的数据,2<=N<=3000, 1<=R<N, -10000<=A[i]<=10000。

反思&题解

比赛思路: 懵,结果暴力也没调出来(不过好像是看错题了)
正解思路: DP,还是懵……
反思: 一天一道毒瘤题,不过还是可以问一下别人做对的
code也还是不放了,等我码出来再说吧……

猜你喜欢

转载自blog.csdn.net/CMC_YXY/article/details/107498712