NOIP2016普及组复赛 解题分析

1.买铅笔

算法分析

不能整除则加1。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
int main()
{
	int n; scanf("%d", &n);
	int snum, sprice;
	int ans = 1e8, t;
	for (int i = 1; i <= 3; ++i)
	{
		scanf("%d%d", &snum, &sprice);
		t = n / snum * sprice + (n % snum == 0 ? 0 : 1) * sprice;
		ans = min(ans, t);
	}
	printf("%d\n", ans);
} 

2.回文日期

算法分析

直接枚举每一个日期然后判断是否回文,肯定超时。注意到,对于固定年份,只能有一个回文日期,比如2142年,其生成的回文日期为21422412,然后判断这个日期是否合法即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
int v[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int shuiwen(int syear) // 1201
{
	return syear * 10000 + syear % 10 * 1000 + syear / 10 % 10 * 100 + syear / 100 % 10 * 10 + syear / 1000;
}
int srun(int syear)
{
	if ((syear % 4 == 0 && syear % 100 != 0) || syear % 400 == 0) return 1;
	else return 0;
}
int sjudge(int s)
{
	int syue = s / 1000 % 10 * 10 + s / 100 % 10;
	int sday = s % 100;
	if (syue < 1 || syue > 12) return 0;
	
	if (srun(s / 10000)) v[2] = 29; else v[2] = 28;
	
	if (sday < 1 || sday > v[syue]) return 0;
	
	return 1;
}
int main()
{
	int sd1, sd2, snum = 0;
	scanf("%d%d", &sd1, &sd2);
	for (int k = sd1 / 10000; k <= sd2 / 10000; ++k)
	{
		int sdata = shuiwen(k); // 生成回文日期 
		if (sjudge(sdata) && sdata >= sd1 && sdata <= sd2) ++snum;
	}
	printf("%d\n", snum);
	return 0;
} 

算法拓展

枚举后面四位,即枚举月份+天数,然后生成年份,效率更高。最多有366种。然后判断生成的年份是否在范围之内即可。

3.海港

算法分析

单调队列维护每艘船的情况。 v i s vis vis数组标记国籍。对于新到来的船只,如果有国籍 x x x,则 + + v i s [ x ] ++vis[x] ++vis[x],如果此时 v i s [ x ] vis[x] vis[x]为1,则答案加1。对于队首的船只,如果要出队,如果有国籍 x x x,则 − − v i s [ x ] --vis[x] vis[x],如果此时 v i s [ x ] vis[x] vis[x]为0,则答案减1。

v e c t o r vector vector存每艘船的人员信息。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define ll long long
using namespace std;
int n, vis[100010];
int q[100010], l, r;
struct node
{
	int t, k;
	vector<int> s;
}sboot[100010];
int main()
{
	int n; scanf("%d", &n);
	int num = 0, x;
	l = 1, r = 0;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d%d", &sboot[i].t, &sboot[i].k);
		for (int j = 1; j <= sboot[i].k; ++j)
		{
			scanf("%d", &x); sboot[i].s.push_back(x);
			if (vis[x] == 0) ++num;
			++vis[x];
		}
		q[++r] = i;
		while (l <= r && sboot[q[l]].t <= sboot[i].t - 86400)
		{
			for (int j = 0; j < sboot[q[l]].s.size(); ++j)
			{
				x = sboot[q[l]].s[j];
				--vis[x];
				if (vis[x] == 0) --num;
			}
			++l;
		}
		printf("%d\n", num);
	}
	return 0;
} 

4.魔法阵

算法分析

根据题目给定的关系,设 x c xc xc x d xd xd之间的距离为 t t t,则 x b xb xb x c xc xc之间的距离为 6 t + k 6t+k 6t+k k k k最小值为1。 x a xa xa x b xb xb之间的距离为 2 t 2t 2t。魔法的取值范围是 [ 1 , n ] [1, n] [1,n],可以用 v i s vis vis数组标记某个魔法值是否被取到,以及被取了几次。设:

c n t [ x ] [ 0 ] cnt[x][0] cnt[x][0]表示:魔法值为 x x x的魔法物品作为A出现的次数。
c n t [ x ] [ 1 ] cnt[x][1] cnt[x][1]表示:魔法值为 x x x的魔法物品作为B出现的次数。
c n t [ x ] [ 2 ] cnt[x][2] cnt[x][2]表示:魔法值为 x x x的魔法物品作为C出现的次数。
c n t [ x ] [ 3 ] cnt[x][3] cnt[x][3]表示:魔法值为 x x x的魔法物品作为D出现的次数。

以上分析中有三个未知量: a a a的位置设为 x x x t t t k k k。枚举这三个未知量,不用具体考虑每个魔法取值是否有物品。比如:

c n t [ a x ] [ 0 ] + = v i s [ b x ] ∗ v i s [ c x ] ∗ v i s [ d x ] cnt[ax][0] += vis[bx] * vis[cx] * vis[dx] cnt[ax][0]+=vis[bx]vis[cx]vis[dx]
以上是计算魔法值 a x ax ax作为A物品出现的次数。假如 b x bx bx没有对应物品,即: v i s [ b x ] = 0 vis[bx] = 0 vis[bx]=0,则最后 v i s [ b x ] ∗ v i s [ c x ] ∗ v i s [ d x ] vis[bx] * vis[cx] * vis[dx] vis[bx]vis[cx]vis[dx]为0,对 c n t [ a x ] [ 0 ] cnt[ax][0] cnt[ax][0]没有贡献。能过约85%的数据。可考虑优化。

假设 a x 、 b x 、 c x 、 d x ax、bx、cx、dx axbxcxdx这四个值构成魔法阵,则 a x 、 b x 、 c x + 1 、 d x + 1 ax、bx、cx+1、dx+1 axbxcx+1dx+1这四个值也能构成魔法阵, a x 、 b x 、 c x + 2 、 d x + 2 ax、bx、cx+2、dx+2 axbxcx+2dx+2也能构成魔法阵,……

计算 c n t [ a x ] [ 0 ] cnt[ax][0] cnt[ax][0]

c n t [ a x ] [ 0 ] + = v i s [ b x ] ∗ v i s [ c x ] ∗ v i s [ d x ] cnt[ax][0] += vis[bx] * vis[cx] * vis[dx] cnt[ax][0]+=vis[bx]vis[cx]vis[dx]
c n t [ a x ] [ 0 ] + = v i s [ b x ] ∗ v i s [ c x + 1 ] ∗ v i s [ d x + 1 ] cnt[ax][0] += vis[bx] * vis[cx+1] * vis[dx+1] cnt[ax][0]+=vis[bx]vis[cx+1]vis[dx+1]
c n t [ a x ] [ 0 ] + = v i s [ b x ] ∗ v i s [ c x + 2 ] ∗ v i s [ d x + 2 ] cnt[ax][0] += vis[bx] * vis[cx+2] * vis[dx+2] cnt[ax][0]+=vis[bx]vis[cx+2]vis[dx+2]
……
……

对于以上的计算,我们可以用类似于前缀和的后缀和优化,维护 v i s [ c x ] ∗ v i s [ d x ] vis[cx]*vis[dx] vis[cx]vis[dx]的后缀和。

然后逆序枚举 d x dx dx c x cx cx,维护 v i s [ a x ] ∗ v i s [ b x ] vis[ax]*vis[bx] vis[ax]vis[bx]的前缀和。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define ll long long
using namespace std;
int n, m, vis[15010], a[40010];
int cnt[15010][4];
int sum[15010];
int main()
{
	scanf("%d%d", &n, &m);
	int x;
	for (int i = 1; i <= m; ++i)
	{
		scanf("%d", &x);
		a[i] = x;
		++vis[x];
	}
	// 正序枚举a和b 
	for (int t = 1; t <= (n - 1) / 9; ++t)
	{
		for (int k = n; k > n - t; --k) sum[k] = 0; 
		for (int k = n - t; k >= 1; --k) sum[k] = vis[k] * vis[k+t] + sum[k+1];
		for (int a = 1; a + 9 * t + 1 <= n; ++a)
		{
			cnt[a][0] += vis[a + 2 * t] * sum[a + 8 * t + 1];
			cnt[a + 2 * t][1] += vis[a] * sum[a + 8 * t + 1];
		} 
	}
	// 倒序枚举d和c
	for (int t = 1; t <= (n - 1) / 9; ++t) 
	{
		for (int k = 1; k < 2 * t; ++k) sum[k] = 0;
		for (int k = 2 * t; k <= n; ++k) sum[k] = vis[k] * vis[k - 2 * t] + sum[k-1];
		for (int d = n; d - (9 * t + 1) >= 1; --d)
		{
			cnt[d][3] += vis[d - t] * sum[d - 7 * t  - 1];
			cnt[d - t][2] += vis[d] * sum[d - 7 * t  - 1];
		}
	}
	for (int i = 1; i <= m; ++i)
	{
		printf("%d %d %d %d\n",cnt[a[i]][0], cnt[a[i]][1], cnt[a[i]][2], cnt[a[i]][3]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值