2020/11/12 Day 2 笔记

· · 个人记录

今天N老师因为要监考,所以布置了一堆AcWing的题目(9道)T89~T98。因本蒟蒻的水平实在太低,并不会做T98“分形之城”,所以今天就分享一下其他几道题的做题经验。

步入正题——(89,90,95题的题解我已经发在昨天的blog上了)

  1. 最短Hamilton路径

给定一张 n 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数n。

接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(记为a[i,j])。

对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。

输出格式

输出一个整数,表示最短Hamilton路径的长度。

数据范围

1≤n≤20

0≤a[i,j]≤107

输入样例:

5

0 2 4 5 1

2 0 6 5 3

4 6 0 8 3

5 5 8 0 5

1 3 3 5 0

输出样例:

18

解析:我第一次看到这道题是崩溃的(因为并不知道hamilton路怎么写),但是从网上查到了hamilton路的方法:

其实这是一道旅行商问题,NP问题(显然我并不知道怎么写),但是发现有的做法使用了状态压缩DP并且写起来很简单。

主要的想法是:从一个点出发,有若干种走法,每走一步都进行比较,并且只选取最短的路径,用每一步的最优解来达到全局最优。所以就是一道很简单的DP题目,状态转移方程:

设i为方案,j为抵达的点,就有:

f[i][j]=min(f[i][j],f[i^(1<<j)][k]+weight[k][j]

下面贴代码:

#include<bits/stdc++.h>
using namespace std;
const int N=20,M=1<<20;
int n;
int f[M][N];
int weight[N][N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            cin>>weight[i][j];
        }
    }
    memset(f,0x3f,sizeof(f));
    f[1][0]=0;
    for(int i=0;i<(1<<n);i++)
    {
        for(int j=0;j<n;j++)
        {
            if((i>>j)&1)
            {
                for(int k=0;k<n;k++)
                {
                    if((i-(1<<j)>>k)&1)
                    {
                        f[i][j]=min(f[i][j],f[i-(1<<j)][k]+weight[j][k]);
                    }
                }
            }
        }
    }
    cout<<f[(1<<n)-1][n-1]<<endl;
    return 0;
}
  1. 递归实现指数型枚举

从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例:

3

输出样例:

3

2

2 3

1

1 3

1 2

1 2 3

这是一个递归部分的板子题,直接DFS+递归就能做出来。递归的作用就是进行判重,并且要对三个状态进行分析:第一个:最大的数字,第二个:当前搜索到的数字,第三个:一共需要搜索多少个数字。

贴代码:

#include<bits/stdc++.h>
using namespace std;
int n;
void dfs(int x,int now)
{
    if(x>n) return;
    if(x==n)
    {
        for(int i=1;i<=n;i++)
            if(now>>(i-1)&1)
                cout<<i<<" ";
        puts("");
    }
    dfs(x+1,now<<1|1);
    dfs(x+1,now<<1);
}
int main()
{
    cin>>n;
    dfs(0,0);
    return 0;
}
  1. 递归实现组合型枚举

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式

两个整数 n,m ,在同一行用空格隔开。

输出格式

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

数据范围 n>0 , 0≤m≤n , n+(n−m)≤25

输入样例:

5 3

输出样例:

1 2 3

1 2 4

1 2 5

1 3 4

1 3 5

1 4 5

2 3 4

2 3 5

2 4 5

3 4 5

分析:这道题的做法和上一道题很相似,进行DFS,用回溯算法就可以写出来,但是要注意只输出m个的情况。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
void dfs(int x,int now,int date)
{
    if(x>n||date+(n-x+1)<m) return;
    if(date==m)
    {
        for(int i=1;i<=n;i++)
            if(now>>(i-1)&1)
                cout<<i<<" ";
        puts("");
        return;
    }
    dfs(x+1,now+(1<<x),date+1);
    dfs(x+1,now,date);
}
int main()
{
    cin>>n>>m;
    dfs(0,0,0);
    return 0;
}
  1. 递归实现排列型枚举

把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数n。

输出格式

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围 1≤n≤9

输入样例:

3

输出样例:

1 2 3

1 3 2

2 1 3

2 3 1

3 1 2

3 2 1

分析:这道题还有一个名字叫全排列,思路还是很相似,依然要用vis数组判重,用DFS深搜就可以出正解了。

代码:

#include<bits/stdc++.h>
using namespace std;
int order[20],n;
bool vis[20];
void cal(int k)
{
    if(k==n+1)
    {
        for(int i=1;i<=n;i++)
            cout<<order[i]<<" ";
        puts("");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        order[k]=i;
        vis[i]=1;
        cal(k+1);
        vis[i]=0;
        order[k]=0;
    }
}
int main()
{
    cin>>n;
    cal(1);
    return 0;
}
  1. 奇怪的汉诺塔

汉诺塔问题,条件如下:

1、这里有A、B、C和D四座塔。

2、这里有n个圆盘,n的数量是恒定的。

3、每个圆盘的尺寸都不相同。

4、所有的圆盘在开始时都堆叠在塔A上,且圆盘尺寸从塔顶到塔底逐渐增大。

5、我们需要将所有的圆盘都从塔A转移到塔D上。

6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。

请你求出将所有圆盘从塔A移动到塔D,所需的最小移动次数是多少。

输入格式

没有输入

输出格式

对于每一个整数n(1≤n≤12),输出一个满足条件的最小移动次数,每个结果占一行。

输入样例:

没有输入

输出样例:

参考输出格式

解析:本来的汉诺塔的递推方程是:d[i]=d[i-1]*2+1(不会真有人不知道吧?不会吧,不会吧)

但是还需要进行的递归是以塔的数目为基础进行计算,每次都去掉一个塔,求剩余塔的解法,状态转移方程是:

f[i]=min(f[i],f[j]*2+d[n-j]);

代码:

#include<bits/stdc++.h>
using namespace std;
int d[21],f[21],i,j;
int main()
{   
    for(i=1;i<=12;i++)
        d[i]=2*d[i-1]+1;
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    for(i=1;i<=12;i++)
        for(j=0;j<i;j++)
            f[i]=min(f[i],f[j]+f[j]+d[i-j]);
    for(i=1;i<=12;i++)
        cout<<f[i]<<endl;
    return 0;
}
  1. 约数之和

假设现在有两个自然数A和B,S是AB的所有约数之和。

请你求出S mod 9901的值是多少。

输入格式

在一行中输入用空格隔开的两个整数A和B。

输出格式

输出一个整数,代表S mod 9901的值。

数据范围

0≤A,B≤5×107

输入样例:

2 3

输出样例:

15

注意: A和B不会同时为0。

解析:

先来ctrl+v一段数学类分析:

质因数分解,就是将一个数分解成为 p1c1×p2c2×…×pncnp1c1×p2c2×…×pncn 比如说24=23×3124=23×31

唯一分解定律,就是字面意思

约数和公式,如下文所示

AB=(1+p11+p12+.....+p1B∗c1)×(1+p22+.....+pnB×c2AB=(1+p11+p12+.....+p1B∗c1)×(1+p22+.....+pnB×c2) sum(p,c)=1+p+p2+…+pcsum(p,c)=1+p+p2+…+pc

然后呢我们可以通过分类以及乘法中的提取公因式法,将sum函数中c为奇数的公式和sum函数中c为偶数的公式推出。

快速幂写法:

int fast(int a,int b)
{
    int ans=1;
    a%=mod;
    while(b)
    {
        if (b&1)
            ans=ans%mod*a;
        a=a%mod*a%mod;
        b>>=1;
    }
    return ans;
}

求和写法:

long long sum(int p,int c)
{
    if (c==0)
        return 1;
    if(c&1)//奇数
        return ((1+fast(p,(c+1)>>1))*sum(p,(c-1)>>1))%mod;
    else//偶数
        return ((1+fast(p,c>>1))*sum(p,(c>>1)-1)+fast(p,c))%mod;
}

完整代码如下:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 9901
int a,b;
int fast(int a,int b)
{
    int ans=1;
    a%=mod;
    while(b)
    {
        if (b&1)
            ans=ans%mod*a;
        a=a%mod*a%mod;
        b>>=1;
    }
    return ans;
}
long long sum(int p,int c)
{
    if (c==0)
        return 1;
    if(c&1)
        return ((1+fast(p,(c+1)>>1))*sum(p,(c-1)>>1))%mod;
    else
        return ((1+fast(p,c>>1))*sum(p,(c>>1)-1)+fast(p,c))%mod;
}
int main()
{
    cin>>a>>b;
    int ans=1;
    for (int i=2;i<=a;i++)
    {
        int s=0;
        while(a%i==0)
        {
            s++;
            a/=i;
        }
        if (s)
            ans=ans*sum(i,s*b)%mod;
    } 
    if (a==0)
        cout<<0<<endl;
    else
        cout<<ans<<endl;
    return 0;
}