AcWing 96. 奇怪的汉诺塔
和一般的汉诺塔不同,这个有四个塔。
在三塔的时候,我们可以理解成,我们放n个圆盘的时候,是先将n-1个圆盘转移到B盘,然后将第n个转移到C盘,再将n-1个圆盘转移回来。
所以状态转移是
f[i]=f[i-1]*2+1;//整体移动两次(i-1)个圆盘 然后加上移动一次第n个圆盘。
当我们四塔的时候,和三塔类似。
因为三塔只有3个塔,在前面n-1个圆盘占据了一个塔之后,相当于只剩下两个塔给他转移了。
所以只能最后一个圆盘转移。
但是四塔的时候,将n-1个圆盘转移到一个塔之后,还剩下三个塔给他进行操作。
所以可以先转移前面一部分到不是D盘到一个盘,然后再转移后面一部分到D盘,
再将前面一部分转移到D盘。
// Problem: 奇怪的汉诺塔
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/98/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Date:2021-10-07 09:03:26
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int inf = 0x3f3f3f3f;
const int MAXN = 1e5 + 10;
int n, m, T;
int flag;
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int f[20];
int a[20];
a[1]=1;
for(int i=2;i<=12;i++){
a[i]=a[i-1]*2+1;//记录的是 三塔状态下转移i个的最小步数。
}
for(int i=1;i<=12;i++){
f[i]=inf;
}
f[1]=1,f[0]=0;
for(int i=2;i<=12;i++){
for(int j=0;j<i;j++){
f[i]=min(f[i],f[j]*2+a[i-j]);
//f[j]表示在四塔时先把j个转移到不是D塔的一个塔
//a[i-j]表示,因为D塔都是比他小的圆盘,所以只有三个塔能用
//则表示三塔时,吧剩下的i-j个转移到D塔
//再f[j]表示四塔时转移到D他的最短步数。
//因为吧i拆分成不同的部分得到=的值不一样 所以要遍历取最小值。
}
}
for(int i=1;i<=12;i++){
cout<<f[i]<<endl;
}
return 0;
}
310. 启示录
一开始以为就是简单的数位dp,想着直接把所有情况找出来,后来才反应过来,记录不了。
只能一次一次的二分查找。然后因为记忆化搜索,每次情况都查找完后,每次查找接近O(logn)所以不会超时。
// Problem: 启示录
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/312/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Date:2021-10-07 09:52:23
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll inf = 500000000000;
const int MAXN = 1e5 + 10;
int n, m, T;
int flag;
int t[MAXN];
int a[30];
ll dp[30][10][2]; //第二维表示之前有几个连续的3
ll dfs(int pos, int num, bool limit /*数位上界变量*/, int flag) //不是每个题都要判断前导零
{
// flag 表示这个数是否已经满足条件
if (num >= 3)
flag = 1;
if (pos == -1)
return flag;
if (!limit && dp[pos][num][flag] != -1)
return dp[pos][num][flag];
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; i++) {
if (i == 6)
ans += dfs(pos - 1, num + 1, limit && i == a[pos], flag);
else
ans += dfs(pos - 1, 0, limit && i == a[pos], flag);
}
if (!limit)
dp[pos][num][flag] = ans;
return ans;
}
ll solve(ll x)
{
int pos = 0;
while (x) //把数位都分解出来
{
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, 0, true, 0); //刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(dp, -1, sizeof(dp));
cin >> T;
while (T--) {
cin >> n;
ll l = 0, r = inf, mid;
while (l < r) {
mid = (l + r + 1) >> 1;
if (solve(mid)<n) {
l = mid;
} else {
r = mid - 1;
}
}
cout << l+1 << endl;
}
return 0;
}
311. 月之谜
这个题目,一开始想是,数位dp,然后dfs下放的是,当前位数前面的位数数字的和,和这个数本身。
但是这样的话,就得相当于暴力搜索,肯定会超时。后来看题解,说是一开始就假设一下mod等于这个数所有位数上的数加起来的值sum,然后数位dp 得到这个数num对mod求余的结果,如果到最后summod并且num0 则表示这个数是魔鬼数。
// Problem: 月之谜
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/313/
// Memory Limit: 64 MB
// Time Limit: 2000 ms
// Date:2021-10-07 10:58:44
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int inf = 0x3f3f3f3f;
const int MAXN = 1e5 + 10;
int mod;
int n, m, T;
int flag;
int a[33];
ll dp[20][200][200]; //不同题目状态不同
ll dfs(int pos, bool limit, int sum,int num) //不是每个题都要判断前导零
{
if (pos == -1) {
if (sum == mod && !num)
return 1;
return 0;
}
if (!limit && dp[pos][sum][num] != -1)
return dp[pos][sum][num];
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; i++) {
ans += dfs(pos - 1, limit && i == a[pos], sum + i, (num * 10 + i) % mod);
}
return dp[pos][sum][num] = ans;
}
ll solve(ll x)
{
int pos = 0;
while (x) //把数位都分解出来
{
a[pos++] = x % 10;
x /= 10;
}
ll ans = 0;
for (mod = 1; mod <200; mod++) {
memset(dp, -1, sizeof(dp));
//因为每次mod都不一样 所以dp数组得初始化
ans += dfs(pos - 1, true, 0, 0);
// cout << ans << endl;
}
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll l, r;
cin >> l >> r;
cout << (solve(r) - solve(l-1)) << endl;
return 0;
}
97. 约数之和
看到的第一眼就是之前那个结论;
唯一分解定理即任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积N=P1a1P2a2P3a3…Pnan,这里P1<P2<P3…<Pn均为质数,其中指数ai是正整数。
然后因为是要求这些数的和,所以再根据约束和定理递归得到结果即可,
// Problem: 约数之和
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/99/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Date:2021-10-07 09:37:32
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll inf = 0x3f3f3f3f;
const ll MAXN = 1e5 + 10;
const ll mod = 9901;
ll n, T;
ll flag;
ll ksm(ll a, ll b)
{
ll ans = 1;
while (b) {
if (b & 1)
ans = (ans * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return ans;
}
ll primes[1000];
ll num[1000];
ll cnt=1;
void devide(ll n)//分解质因数
{
for (ll i = 2; i <= n / i; i++) { //因为n一直在变小, 当n小于i的平方的时候,则表示后面已经没有满足的质数了
if (n % i == 0) {
primes[cnt]=i;
while (n % i == 0) { //求出是这个质数的几次方
num[cnt]++;
n /= i;
}
cnt++;
}
}
if (n > 1) { //如果还有剩下的。
num[cnt]=1;
primes[cnt++]=n;
}
}
ll sum(ll a, ll b)
{
if (b == 1)
return 1;
if(b % 2 == 0) {
return (ksm(a, b / 2) + 1) * sum(a, b / 2) % mod;
}
return (ksm(a,b - 1) + sum(a, b - 1)) % mod;
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll a, b;
cin >> a >> b;
if (a == 0) {
cout << 0 << endl;
return 0;
}
ll ans = 1;
// cout<<1;
devide(a);
// cout<<1;
for (ll i=1;i<cnt;i++) {
ll p=primes[i],k=num[i]*b;
// cout<<p<<" "<<k<<endl;
ans = (ans * sum(p,k +1)) % mod;
// ans = (ll)ans * sum(p, k + 1) % mod;
//一开始想直接把sum里面的判断换一下,但是确实0次方也是需要考虑的 所以+1即可处理
//因为是 b次方 而sum求的是到b-1项 所以要+1才行。
}
cout << ans << endl;
return 0;
}
98.分形之城
看半天题目没看懂题意,直接看的题解。
题意就是那张图,让你看着图片找出他的规律。
找出规律后,每次转移后 再将原点转换即可。
// Problem: 分形之城
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/100/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Date:2021-10-07 20:28:44
// 算法:
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int inf=0x3f3f3f3f;
const int MAXN=1e5+10;
int n,m,T;
int flag;
typedef pair<ll,ll> pll ;
pll digui(ll n,ll m){
if(n==0){
// cout<<"xcxz";
return {0,0};
}
ll len=1ll<<(n-1);//表示转移其与原点需要转移的距离。
ll cnt=1ll<<(2*n-2);//表示的是 将这个等级的数分成四份 每一份是多少城市
pll pos=digui(n-1,m%cnt);//表示在上个等级的城市中是该部分的编号。
ll pact=m/cnt;//表示在坐标轴哪个界限
ll x=pos.first,y=pos.second;
// cout<<x<<" "<<y<<endl;
if(pact == 0) return {-y-len,-x+len};
if(pact == 1) return {x + len,y+len};
if(pact == 2) return {x + len,y - len};
return {y-len,x - len};
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>T;
while(T--){
ll a,b;
cin>>n>>a>>b;
pll da=digui(n,a-1);//之所以减1 是为了便于第35行
// m%cnt表示在上个等级的城市区域的编号,即 0~15分别表示1~16
pll db=digui(n,b-1);
double x=da.first-db.first;
double y=da.second-db.second;
// cout<<x<<" "<<y<<endl;
printf("%.0lf\n",sqrt(x*x+y*y)*5);
}
return 0;
}