NOIP2014普及组复赛 解题分析

1.珠心算测试

算法分析

有人可能会误读题意。题意说的是,有多少个数,能够由集合中的另外两个数构成。不是说集合中的数a、b、c,有多少对能组成等式“a + b = c”。下面数据:

5
1 2 3 4 5

答案是3,不是4。

枚举每一个数,然后再枚举集合中的数,判断能否构成,一旦找到break退出。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;
int a[110];
int main()
{
  int n,k,sum=0;
  cin>>n;
  for (int i=1;i<=n;++i)
  	cin>>a[i];
  
  for (int k = 1; k <= n; ++k)  // 枚举每一个数字   
  {
  	    int ok = 0;
	    for (int i = 1; i < n; ++i)
	  	{
	  		for (int j = i + 1; j <= n; ++j)
	  			if (a[k] == a[i] + a[j])
	  			{
	  				++sum;
	  				ok = 1;
	  				break;
	  			}
	  			if (ok == 1) break;
	  	}
  }
  
  cout<<sum<<endl;
  return 0;
}

算法拓展

普及组的比赛中经常会用到标记思想,有时也叫桶的思想或hash思想。

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int a[110], f[20010], vis[20010];  // 
int main()
{ 
	// P2141 [NOIP2014 普及组] 珠心算测验
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &a[i]);
		f[a[i]] = 1;  // 核心  
	}
	// 统计 
	int ans = 0;
	for (int i = 1; i < n; ++i)
		for (int j = i + 1; j <= n; ++j)
		{
			int t = a[i] + a[j];
			if (vis[t] == 0)
			{
				if (f[t] == 1) ++ans;
				vis[t] = 1;
			}			
		}
	printf("%d\n", ans);
	return 0;
}

2.比例简化

算法分析

L L L很小,直接枚举 A ′ A' A B ′ B' B。要满足 A ′ A' A B ′ B' B互质、 A ′ / B ′ > = A / B A'/B' >= A / B A/B>=A/B的情况下 A ′ / B ′ A'/B' A/B最小。用 a n s a ansa ansa a n s b ansb ansb记录结果。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#define ll long long
using namespace std;
int A, B, L;
int gcd(int a, int b)
{
	return b ? gcd(b, a%b) : a;
}
int main()
{
	scanf("%d%d%d", &A, &B, &L);
	double val = A * 1.0 / B;
	int ansa, ansb;
	double minval = 1e8;
	for (int a = 1; a <= L; ++a)
		for (int b = 1; b <= L; ++b)
			if (gcd(a, b) == 1 && a * 1.0 / b >= val)
			{
				if (a * 1.0 / b < minval)
				{
					minval = a * 1.0 / b;
					ansa = a; ansb = b;
				}
			}
	printf("%d %d\n", ansa, ansb);
	return 0;
}

3.螺旋矩阵

算法分析

n n n最大值为30000,暴力枚举肯定超时,过不了全部数据。思考发现,矩阵是一圈圈的,外圈比内圈多了8个数字。如果能直接定位 ( i , j ) (i, j) (i,j)处在第几个圈上,比如处在第 t t t圈,利用前缀和能直接得出前 t − 1 t-1 t1圈的数字个数。然后判断 ( i , j ) (i, j) (i,j)处在第 t t t圈的哪条边上,可以快速计算。

如果 n n n是偶数,最后一圈的数字个数是4,如果 n n n是奇数,最后一圈的数字个数是1。求前缀和的时候,得讨论 n n n的奇偶性。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
ll sum[30010];
int n, x, y;
struct node
{
	int zc, bc; // zc:周长  bc:边长  
}a[30010];
int main()
{
	scanf("%d%d%d", &n, &x, &y);
	int s = n, sn;
	if (n % 2) 	sn = n / 2 + 1;	// sn:圈数 	
	else sn = n / 2;
	
	for (int i = 1; i < sn; ++i) 
	{
		a[i].zc = s * 4 - 4;
		a[i].bc = s;
		s -= 2;
	}
	if (n % 2) a[sn].zc = 1, a[sn].bc = 1; else a[sn].zc = 4, a[sn].bc = 2;
	
	sum[1] = a[1].zc;
	for (int i = 2; i <= sn; ++i) sum[i] = sum[i-1] + a[i].zc;
	
	int t = min(x, min(y, min(n + 1 - x, n + 1 - y)));
	// (x, y)处在第t圈 
	ll ans = sum[t-1];
	int k = a[t].bc; // 第t圈的边长  
	if (x == t)
	{
		ans += y - t + 1;
	}else if (n + 1 - x == t)
	{
		ans += 2 * k - 1 + k - (y - t + 1);
	}else if (y == t)
	{
		ans += 3 * k - 2 + k - (x - t + 1);
	}else if (n + 1 - y == t)
	{
		ans += k + x - t;
	}
	printf("%lld\n", ans);
	return 0;
}

4.子矩阵

算法分析

最直接的想法是dfs套dfs。先搜出 r r r行,再搜出 c c c列,统计比较结果。以下暴力算法能得70分。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, b[20][20];
int ans = 1e8;
void dfsc(int d, int sum)
{
	if (sum > ans) return;
	for (int i = lie[d-1] + 1; i <= m; ++i)
	{
		if (d - 1 + m - d + 1 < c) break;  
		lie[d] = i;
		int s1 = 0, s2 = 0;
		for (int k = 2; k <= r; ++k) s1 += abs(a[ hang[k] ][ lie[d] ] - a[ hang[k-1] ][ lie[d] ]);
		for (int k = 1; k <= r; ++k) s2 += abs(a[ hang[k] ][ lie[d] ] - a[ hang[k] ][ lie[d-1] ]);
		if (d == 1)
		{
			sum += s1;
		}else 
		{
			sum += s1 + s2;
		}
		if (d == c) ans = min(ans, sum);
		else dfsc(d + 1, sum);
		
		if (d == 1)
		{
			sum -= s1;
		}else 
		{
			sum -= s1 + s2;
		}
	}
}
void dfsr(int d)
{
	for (int i = hang[d-1] + 1; i <= n; ++i)
	{
		if (d - 1 + n - d + 1 < r) break;  
		hang[d] = i;
		if (d == r) dfsc(1, 0);
		else dfsr(d + 1);
	}
}
int main()
{
	scanf("%d%d%d%d", &n, &m, &r, &c);
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
	dfsr(1);
	printf("%d\n", ans);
	return 0;
}

以上代码dfsc中,计算 s 1 s1 s1 s 2 s2 s2的过程每次都要重复计算,造成了时间的浪费。可以预处理出来这些值。设:

h v a l [ i ] [ x ] [ j ] hval[i][x][j] hval[i][x][j]:第 i i i行和第 x x x行在第 j j j列上的数值之差的绝对值。
l v a l [ j ] [ x ] [ i ] lval[j][x][i] lval[j][x][i]:第 j j j列和第 x x x列在第 i i i行上的数值之差的绝对值。
l i e s u m [ j ] : liesum[j]: liesum[j]每列的所有相邻行的数值之差的绝对值和。
h a n g s u m [ j ] [ x ] : hangsum[j][x]: hangsum[j][x]在所有行上第 j j j列和第 x x x列之间的数值之差的绝对值之和。

搜行的时候,每搜出一行,就累加 l i e s u m [ j ] liesum[j] liesum[j] h a n g s u m [ j ] [ x ] hangsum[j][x] hangsum[j][x]。在搜列的时候,直接使用就行了。这样优化,能得80分。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, hval[20][20][20], lval[20][20][20];
int liesum[20], hangsum[20][20];
int ans = 1e8;
void dfsc(int d, int sum)
{
	if (sum > ans) return;
	for (int j = lie[d-1] + 1; j <= m; ++j)
	{
		if (d - 1 + m - d + 1 < c) break;  
		lie[d] = j;
		sum += liesum[j] + hangsum[lie[d-1]][j];
		if (d == c) ans = min(ans, sum);
		else dfsc(d + 1, sum);	
		
		sum -= liesum[j] + hangsum[lie[d-1]][j];	
	}
}
void dfsr(int d)
{
	for (int i = hang[d-1] + 1; i <= n; ++i)
	{
		if (d - 1 + n - d + 1 < r) break;  
		hang[d] = i;
		if (d != 1)
		{
			for (int j = 1; j <= m; ++j) liesum[j] += hval[ hang[d-1] ][i][j];
		}
		for (int j = 1; j < m; ++j)
			for (int x = j + 1; x <= m; ++x)
				hangsum[j][x] += lval[j][x][i];
				
		if (d == r) dfsc(1, 0);
		else dfsr(d + 1);
		
		if (d != 1)
		{
			for (int j = 1; j <= m; ++j) liesum[j] -= hval[ hang[d-1] ][i][j];
		}
		for (int j = 1; j < m; ++j)
			for (int x = j + 1; x <= m; ++x)
				hangsum[j][x] -= lval[j][x][i];		
	}
}
int main()
{
	scanf("%d%d%d%d", &n, &m, &r, &c);
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
	// hval
	for (int i = 1; i < n; ++i)
		for (int x = i + 1; x <= n; ++x)
			for (int j = 1; j <= m; ++j)
				hval[i][x][j] = abs(a[i][j] - a[x][j]);
	// lval
	for (int j = 1; j < m; ++j)
		for (int x = j + 1; x <= m; ++x)
			for (int i = 1; i <= n; ++i)
				lval[j][x][i] = abs(a[i][j] - a[i][x]);
	dfsr(1);
	printf("%d\n", ans);
	return 0;
}

翻看其他人的博客,发现有用以上算法过了的。不同的地方在 d f s c dfsc dfsc的实现。上面代码中用 l i e lie lie数组存储已经搜索过的每一列,这样每次调用的时候,造成了额外开销。把搜索过的信息保存在参数列表中,就会快很多。这样就能过了。以下代码重点看 d f s c dfsc dfsc的实现,100分。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, hval[20][20][20], lval[20][20][20];
int liesum[20], hangsum[20][20];
int ans = 1e8;
void dfsc(int x, int t, int sum) // 上一个是x,共t个,累计值为sum,用lie[]存的话,有几个点超时  
{
	if (t == c)
	{
		ans = sum; return;
	}
	for (int j = x + 1; j <= m; ++j)
	{
		if (t + m - j + 1 < c) break;  
		int s = 0;
		s = sum + liesum[j] + hangsum[x][j];
		if (s < ans) dfsc(j, t + 1, s);	
	}
}
void dfsr(int d)
{
	for (int i = hang[d-1] + 1; i <= n; ++i)
	{
		if (d - 1 + n - d + 1 < r) break;  
		hang[d] = i;
		if (d != 1)
		{
			for (int j = 1; j <= m; ++j) liesum[j] += hval[ hang[d-1] ][i][j];
		}
		for (int j = 1; j < m; ++j)
			for (int x = j + 1; x <= m; ++x)
				hangsum[j][x] += lval[j][x][i];
				
		if (d == r) dfsc(0, 0, 0);
		else dfsr(d + 1);
		
		if (d != 1)
		{
			for (int j = 1; j <= m; ++j) liesum[j] -= hval[ hang[d-1] ][i][j];
		}
		for (int j = 1; j < m; ++j)
			for (int x = j + 1; x <= m; ++x)
				hangsum[j][x] -= lval[j][x][i];		
	}
}
int main()
{
	scanf("%d%d%d%d", &n, &m, &r, &c);
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
	// hval
	for (int i = 1; i < n; ++i)
		for (int x = i + 1; x <= n; ++x)
			for (int j = 1; j <= m; ++j)
				hval[i][x][j] = abs(a[i][j] - a[x][j]);
	// lval
	for (int j = 1; j < m; ++j)
		for (int x = j + 1; x <= m; ++x)
			for (int i = 1; i <= n; ++i)
				lval[j][x][i] = abs(a[i][j] - a[i][x]);
	dfsr(1);
	printf("%d\n", ans);
	return 0;
}

算法拓展

dfs+dp。

在搜完行的时候,我们用 l i e s u m liesum liesum记录了每列的所有相邻行的数值之差的绝对值和。下面的思路就是压行。将所有行在每列上的信息看作 l i e s u m liesum liesum数组的一个点。下面就是在该数列上选择 c c c个点,使值最小。问题就化成了dp了。

f [ i ] [ j ] f[i][j] f[i][j]:规划到了第 i i i列,总共取了 j j j列,且第 i i i列必取的最小值。

void swork()
{
	memset(f, 0x3f, sizeof(f));
	for (int i = 1; i <= m; ++i) f[i][1] = liesum[i];
	for (int i = 2; i <= m; ++i)
		for (int j = 2; j <= i; ++j)
			for (int k = j - 1; k <= i - 1; ++k)
			f[i][j] = min(f[i][j], f[k][j-1] + liesum[i] + hangsum[k][i]);
	for (int j = c; j <= m; ++j) ans = min(ans, f[j][c]);
	
}

完整代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, hval[20][20][20], lval[20][20][20];
int liesum[20], hangsum[20][20];
int f[20][20]; // f[i][j]:规划到了第i列,总共取了j列,且第i列必取  
int ans = 1e8;
void swork()
{
	memset(f, 0x3f, sizeof(f));
	for (int i = 1; i <= m; ++i) f[i][1] = liesum[i];
	for (int i = 2; i <= m; ++i)
		for (int j = 2; j <= i; ++j)
			for (int k = j - 1; k <= i - 1; ++k)
			f[i][j] = min(f[i][j], f[k][j-1] + liesum[i] + hangsum[k][i]);
	for (int j = c; j <= m; ++j) ans = min(ans, f[j][c]);
	
}
void dfsr(int d)
{
	for (int i = hang[d-1] + 1; i <= n; ++i)
	{
		if (d - 1 + n - d + 1 < r) break;  
		hang[d] = i;
		if (d != 1)
		{
			for (int j = 1; j <= m; ++j) liesum[j] += hval[ hang[d-1] ][i][j];
		}
		for (int j = 1; j < m; ++j)
			for (int x = j + 1; x <= m; ++x)
				hangsum[j][x] += lval[j][x][i];
				
		if (d == r) swork();
		else dfsr(d + 1);
		
		if (d != 1)
		{
			for (int j = 1; j <= m; ++j) liesum[j] -= hval[ hang[d-1] ][i][j];
		}
		for (int j = 1; j < m; ++j)
			for (int x = j + 1; x <= m; ++x)
				hangsum[j][x] -= lval[j][x][i];		
	}
}
int main()  // dfs + dp 
{
	scanf("%d%d%d%d", &n, &m, &r, &c);
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
	// hval
	for (int i = 1; i < n; ++i)
		for (int x = i + 1; x <= n; ++x)
			for (int j = 1; j <= m; ++j)
				hval[i][x][j] = abs(a[i][j] - a[x][j]);
	// lval
	for (int j = 1; j < m; ++j)
		for (int x = j + 1; x <= m; ++x)
			for (int i = 1; i <= n; ++i)
				lval[j][x][i] = abs(a[i][j] - a[i][x]);
	dfsr(1);
	printf("%d\n", ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值