ABC336 A-F

明天F补完题就加上()

放假了又能网瘾漏w

上周的abc太简单了这周的好难,E出了个数位dp想法以为是想复杂了结果还真是...

Tasks - AtCoder Beginner Contest 336

A - Long Loong

题意:

输出龙的拼音,把'o'换成连续的n个'o'

代码:

void solve()
{
	int n;
	scanf("%d", &n);
	printf("L");
	while (n--)printf("o");
	printf("ng\n");
}

B - CTZ

题意:

输出n的二进制表示末尾有多少个连续的0

代码:

直接bitset偷懒了

void solve()
{
	int x;
	scanf("%d", &x);
	bitset<40>st = x;
	for (int i = 0;; ++i)
	{
		if (st[i] != 0)
		{
			printf("%d", i);
			return;
		}
	}
}

C - Even Digits

题意:

输出仅由偶数构成的十进制数中第k小的数

题解:

看成五进制即可,记得特判n=1的情况

void solve()
{
	LL n;
	scanf("%lld", &n);
	vector<int>ans;
	--n;
	if (n == 0)
	{
		printf("0");
		return;
	}
	while (n)
	{
		ans.push_back(n % 5);
		n /= 5;
	}
	while (ans.size())
	{
		printf("%d", ans.back() * 2);
		ans.pop_back();
	}
}

D - Pyramid

题意:

给出一个长度为n的正整数数组,你可以进行以下两种操作:

1:将一个元素-1 

2:在数组开头或者末尾删除一个元素

使得数组最终变成1,2,3,...,k-1,k,k-1,...,3,2,1的形式,求可能的最大的k

题解:

把每个ai想象成高度,找最大的k相当于是在这个图里找一个最大的金字塔形的东西,例如样例1:

22311   121
  #
###   -> #
#####   ###

l[i]表示点i在左斜边上时i位置的最大高度,转移为l[i] = min(l[i - 1] + 1, a[i]),r[i]同理,答案为max({ min(l[i], r[i]) }),具体做法见代码

int a[N], l[N], r[N];
void solve()
{
	int n, ans = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
		scanf("%d", &a[i]);
	for (int i = 1, j = n; i <= n; ++i, --j)
	{
		l[i] = min(l[i - 1] + 1, a[i]);
		r[j] = min(r[j + 1] + 1, a[j]);
	}
	for (int i = 1; i <= n; ++i)
		ans = max(ans, min(l[i], r[i]));
	printf("%d\n", ans);
}

E - Digit Sum Divisible

题意:

一个数的数位和为他的十进制表示上的所有数的和,当一个数能被他的数位和整除我们成这个数是好的数,求从0到n一共有多少个好的数

题解:

先枚举所有可能的数位和,对于每个数位和m我们做一个dp求数位和为m且能被m整除的小于等于n的数的数量:

设dp[i][j][k]为对于十进制数的前i位,数位和为j,该数当前的值取模m后的余数为k时的方案数,对于每个确定的dp[i][j][k],它转移到下一位时,设下一位填入的数为x,转移为:dp[i - 1][j + x][(k + x * pow(10, i - 1) % m] += dp[i][j][k],关于限制当前数小于等于n,我们只需要在求完每一位数之后把超过n的部分减去即可。大致思路是这样,具体做法见代码

题解中的数位和我在代码中用的是剩余的数位和,思路是一样的就是和题解中的转移j要反一下

LL pw[20], dp[16][140][140];
void solve()
{
	LL n, ans = 0;
	scanf("%lld", &n);
	LL t = n, mx = 0;
	vector<int>a;
	while (t)
		mx += t % 10, a.push_back(t % 10), t /= 10;
	mx = max(mx, a.back() - 1 + ((LL)a.size() - 1) * 9);
	pw[0] = 1;
	for (int i = 1; i < 20; ++i)
		pw[i] = pw[i - 1] * 10;
	for (int m = 1; m <= mx; ++m)//枚举数位和m
	{
		LL s = m, md = 0;//按n的每一位选数时的数位和和余数
		memset(dp, 0, sizeof dp);
		dp[a.size()][m][0] = 1;
		for (int i = a.size(); i > 0; --i)//第i位,准备填第i-1位
		{
			for (int j = 0; j <= m; ++j)//填数前数位和还剩j
			{
				for (int k = 0; k < m; ++k)//余数是k
				{
					for (int x = 0; x <= 9 && j - x >= 0; ++x)//第i-1位填x的转移
						dp[i - 1][j - x][(k + x * pw[i - 1]) % m] += dp[i][j][k];
				}
			}
			//减去超过上界的
			for (int x = a[i - 1] + 1; x <= 9 && s - x >= 0; ++x)
			{
				int j = s, k = md;
				dp[i - 1][j - x][(k + x * pw[i - 1]) % m] -= 1;
			}
			s -= a[i - 1], md = (md + a[i - 1] * pw[i - 1]) % m;
		}
		ans += dp[0][0][0];
	}
	printf("%lld\n", ans);
}

写的挺抽象的,应该还有更好的做法

F - Rotation Puzzle

题意:

给出一个n行m列的二维矩阵,其中的数字是一个排列(从1到n*m且两两不同),你可以在其中选择一个n-1行m-1列的子矩阵然后将它旋转180度,问最少多少步能将矩阵变成形如{1,2,3},{4,5,6},{7,8,9}的形式(看样例图能好理解),最多执行20步操作,20步内无法完成输出-1

题解:

题目限制了操作步数小于20,对于每个矩阵我们有四种变换方式,去掉往回走的操作直接暴搜复杂度是O(3^20*n*m)(n*m是变换、存储矩阵需要的常数),显然是不能接受的复杂度。此时我们可以双向广搜,显然旋转与旋转的逆操作时一样的,对终点和起点都搜至多10步,对起点和终点搜到的交集求一下它到起点、终点的最短路的和,取最小值就是最终答案。感觉挺典的,题目给了不超过20步好像有点太明显是暴搜优化了...

define真好用,这个代码我本地跑了感觉好慢交上去居然直结果了

typedef vector<vector<int>>grid;
#define makegrid(a) grid a(n,vector<int>(m))
int n, m;
map<grid, int>mpa, mpb;
grid fun(grid g, int ux, int uy)//旋转以ux,uy为左上角的大小为(n-1)*(m-1)的子矩阵
{
	makegrid(res);
	res = g;
	for (int i = 0; i < n - 1; ++i)
	{
		for (int j = 0; j < m - 1; ++j)
		{
			int tx = n - 2 - i, ty = m - 2 - j;
			res[ux + i][uy + j] = g[ux + tx][uy + ty];
		}
	}
	return res;
}
void bfs(grid g, map<grid, int>&mp)
{
	queue<pair<grid, int>>q;
	q.push({ g ,0 });
	mp[g] = 0;
	while (q.size())
	{
		grid u = q.front().first;
		int step = q.front().second;
		q.pop();
		if (step >= 10)return;
		for (auto x : { 0,1 })
		{
			for (auto y : { 0,1 })
			{
				grid v = fun(u, x, y);
				if (mp.find(v) == mp.end())
				{
					mp[v] = step + 1;
					q.push({ v,step + 1 });
				}
			}
		}
	}
}
void solve()
{
	scanf("%d%d", &n, &m);
	makegrid(a);
	makegrid(b);
	for (int i = 0, tot = 0; i < n; ++i)
	{
		for (int j = 0; j < m; ++j)
		{
			scanf("%d", &a[i][j]);
			b[i][j] = ++tot;
		}
	}
	bfs(a, mpa);
	bfs(b, mpb);
	int ans = INF;
	for (auto& it : mpa)
	{
		if (mpb.find(it.first) != mpb.end())
			ans = min(ans, it.second + mpb[it.first]);
	}
	if (ans == INF)printf("-1\n");
	else printf("%d\n", ans);
}

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值