PTA数据结构专项练习

12.n皇后问题
11.最少失约
10.气球升起来
9.奇数平方和
8.0-1背包问题
7.最长有序子序列
6.铺满方格
5.计算n!(n要能大于13)
3.八皇后问题
2.哈夫曼编码
1.愿天下有情人都是失散多年的兄妹


12.n皇后问题

在这里插入图片描述

八皇后问题升级版

#define fst first
#define sed second
#define pb push_back

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N=40;
int n, ans;
int g[N][N];
bool row[N], col[N], X1[2*N], X2[2*N];

// 检查该点是否OK
bool check(int x, int y){
    if(!row[x])
    if(!col[y])
    if(!X1[x-y+15])
    if(!X2[x+y])
        return 1;
    return 0;
}

// 改变 x,y 点的状态
bool change(int x, int y){
    // X1 (x-y+15)
    // X2 (x+y)
    row[x]^=1;
    col[y]^=1;
    X1[x-y+15]^=1;
    X2[x+y]^=1;
}

// 当前在第几行
void dfs(int u){
    if(u>n){
        ans++;
        return ;
    }

    for(int j=1; j<=n; j++)
        if( check(u, j) ) {
            change(u, j);
            dfs(u+1);
            change(u, j);
        }
}

void solve(){
    memset(g, 0, sizeof g);
    memset(col, 0, sizeof col);
    memset(row, 0, sizeof row);
    memset(X1, 0, sizeof X1);
    memset(X2, 0, sizeof X2);
    ans=0;
    // 初始化
    // 从第一行开始搜
    dfs(1);

    cout<<ans<<"\n";
}

int main(){
    //int T; cin>>T; while(T--)
    while(cin>>n) solve();
    return 0;
}

11.最少失约

在这里插入图片描述

不会做,那就暴搜吧

在更新答案时,如果当前失约次数已经大于答案,就没必要更新了
值得注意的是,在搜索时,最后一个点不一定能搜索到,所以需要在最后一个点开一层去下一个点

#define fst first
#define sed second
#define pb push_back

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 1e5;

int n, ans;
PII g[N];
bool used[N]; // 1表示使用了

// 更新答案
void updata() {
    int res = 0;
    // 记录失约次数
    for (int i = 1; i <= n; i++)
        if (!used[i]) {
            res++;
            if (res > ans) return;
        }
    ans = min(ans, res); // 更新答案
    return;
}

// 当前在第几个点
void dfs(int u) {

    if (u >= n) {
        updata();
        return;
    }

    for (int i = u + 1; i <= n; i++) {
        // 选择
        if (g[i].fst >= g[u].sed) {
            used[i] = 1;
            dfs(i);
            used[i] = 0; // 恢复现场
        }
        // 不选择

        // 
        if (i == n) dfs(i);
    }

}

void solve() {
    memset(g, 0, sizeof g);
    cin >> n; // 今天活动总数
    ans = n;  // 全部失约

    for (int i = 1; i <= n; i++)
        scanf("%d%d", &g[i].fst, &g[i].sed);
    sort(g + 1, g + n + 1);

    // 不会做,就暴搜

    dfs(0);
    cout << ans;
    return;
}

int main() {
    int T; cin >> T; while (T--)
        solve();
    return 0;
}

10.气球升起来

在这里插入图片描述

有序哈希

#include <iostream>
#include <map>
using namespace std;

void solve(int n){
    map<string, int> H;
    string str;

    while(n--){
        cin>>str;
        H[str]++;
    }

    string ans;
    int cnt=0;

    for(auto i:H)
        if(cnt<i.second){
            ans=i.first;
            cnt=i.second;
        }
    cout<<ans<<"\n";
}

int main(){
    int n; 
    while(cin>>n) solve(n);
    return 0;
}

9.奇数平方和

在这里插入图片描述

直接pow应该也是可得

#define fst first
#define sed second
#define pb push_bak

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef pair<int, int> PII;
typedef long long LL;

const int N=2e5+10;

// a^b%MOD
LL quick_pow(LL a, int b){
    LL res=1;//%MOD;
    for( ; b; b>>=1){
        if(b&1) res=1LL*a*res;//%MOD; // 决定是否相乘
        a=1ll*a*a;//%MOD;             // 每个位置上,递推出的二进制位上的值
    }
    return res;
}

void solve(int n){
    LL res=0;
    for(int i=1; i<=n; i+=2)
        res+=quick_pow(i, 2);
    cout<<res<<"\n";
}

int main(){
    //int T; cin>>T; 
    int n;
    while(cin>>n) 
    solve(n);
    return 0;
}

8.0-1背包问题

在这里插入图片描述

朴素 01 背包
dp[i][j] 指前 i 个物品在容量 j 下的最大价值
dp[i][j] 可以通过两种状态转移而来
1:dp[i-1][j] 在不选择第i个物品的情况下,dp[i][j] 就是在 j 容量下,只选择前 i-1 个物品的最大价值
2:dp[i-1][j-V[i]] + W[i] 若一定要选择 i 物品,即 i 物品一定要存在于背包中,可以形象的理解为,拿出现在背包中体积之和刚好为 V[i] 的物品集合,即让背包中存在一个 V[i] 大小的空间,那么就是 j-V[i]。显然,如果当前背包容量小于V[i],是不可能放下的

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=2e3+10;

// 前i个物品在容量j的背包下的最大价值
int dp[N][N];

// 体积 价值
int V[N], W[N];

void solve(int n){
    //memset(dp, 0, sizeof dp);
    
    int m; // 容量
    cin>>m;
    for(int i=1; i<=n; i++) scanf("%d", &V[i]);
    for(int i=1; i<=n; i++) scanf("%d", &W[i]);
    
    for(int i=1; i<=n; i++)
        for(int j=0; j<=m; j++){
            dp[i][j]=dp[i-1][j];
            if( j >= V[i] )
                dp[i][j]=max( dp[i-1][j], dp[i-1][j-V[i]]+W[i] );
        }

    cout<<dp[n][m]<<endl;
}

int main(){
    int n;
    while(cin>>n) 
        solve(n);
    return 0;
}

7.最长有序子序列

在这里插入图片描述

最长上升子序列 LIS
值得注意,dp[i]的含义是,以 g[i] 结尾的序列 的最长长度,因此 dp[n] 并不是最长长度

#include <iostream>
#include <cstring>
using namespace std;

const int N=1e3+10;

int g[N];
// 以 i 结尾 的最长子序列长度
int dp[N];

int T;

bool sb=0;

void solve(int t){
    memset(dp, 0, sizeof dp);
    //memset(g, 0, sizeof g);
    int n;
    cin>>n;
    for(int i=1; i<=n; i++) scanf("%d", &g[i]);

    for(int i=1; i<=n; i++){
        dp[i]=1;
        for(int j=1; j<i; j++)
            if(g[i]>g[j])
                dp[i]=max(dp[i], dp[j]+1);
        dp[0]=max(dp[0], dp[i]);
    }
    
    if(sb)
        cout<<"\n\n";
    cout<<dp[0];
    sb=1;
}

int main(){
    cin>>T; 
    while(T--)
        solve(T);
    return 0;
}

6.铺满方格

在这里插入图片描述

经典DP,当前状态可以由前面三种状态转移而来
dp[i]=dp[i-1]+dp[i-2]+dp[i-3]

#include <iostream>
#include <cstring>
using namespace std;

typedef long long LL;

const int N=60;
// 铺满前 i 长度的格子有 dp[i]种方案
LL dp[N];

void solve(int n){
    // 初始化
    dp[1]=1; // 1
    dp[2]=2; // 11 2
    dp[3]=4; // 111 12 21 3
    for(int i=4; i<=n; i++)
        dp[i]=dp[i-3]+dp[i-2]+dp[i-1];
    cout<<dp[n]<<endl;
}

int main(){
    int n;
    while(cin>>n) {
        memset(dp, 0, sizeof dp);
        solve(n);
    }
    return 0;
}

5.计算n!(n要能大于13)

在这里插入图片描述

这题竟然不让用python过

直接上高精度乘法就ok

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

vector<int> MUL(vector<int>&A, int b){
    // 倒叙输入A,正序b,倒叙输出res
    vector<int> res;
    int t=0;
    for(int i=0; i<A.size() || t; i++){
        if (i<A.size()) t+=A[i]*b;
        res.push_back(t % 10);
        t /= 10;
    }
    // 去前导0
    while (res.size() > 1 && res.back() == 0) res.pop_back();
    return res;
}

int main(){
    int n;
    cin>>n;
    
    vector<int> ans;
    ans.push_back(1);
    
    for(int i=1; i<=n; i++)
        ans=MUL(ans, i);
    
    for(int i=ans.size()-1; i>=0; i--)
        cout<<ans[i];
    
    return 0;
}

3.八皇后问题

在这里插入图片描述

经典dfs,其中比较关键的是斜线状态的表示
在这里插入图片描述

主对角线可以用 r-c+10 唯一表示
副对角线可以用 r+c 唯一表示

明确了每个状态的表示,那么就可以从 (1, 1) 点开始,挨个搜索,这也是最朴素的搜索方法,但可惜,tle了
根据题意,每行必定只会存在一个queen,那么我们可以不一个一个的点搜,而是一行一行的搜,当前行若存在皇后,下一个皇后必定不在该行

#include <iostream>
using namespace std;

const int N=20;

int n, flg;

int g[N][N];
int row[N], col[N], x1[2*N], x2[2*N];

bool use(int r, int c){
    if(row[r] || col[c] || x1[r-c+10] || x2[r+c])
        return 1;
    return 0;
}

void change(int r, int c){
    g[r][c]^=1;
    row[r]^=1;
    col[c]^=1;
    x1[r-c+10]^=1;
    x2[r+c]^=1;
}

// 搜索行
void dfs(int r){

    if(r>n){
        if(flg!=0) puts(""); // 格式
        for(int i=1; i<=n; i++, puts(""))
            for(int j=1; j<=n; j++){
                if(g[i][j]) putchar('Q');
                else putchar('.');
                if(j!=n) putchar(' ');
            }
        flg=1;
        return ;
    }

    // 枚举状态
    for(int c=1; c<=n; c++)
        // 改变
        if(!use(r, c)){
            change(r, c);
            dfs(r+1);
            change(r, c); // 恢复现场
        }
}

int main(){
    cin>>n;
    
    dfs(1);

    if(!flg) puts("None");

    return 0;
}

2.哈夫曼编码

在这里插入图片描述

当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的wpl(带权路径长度)最小,称这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”

从定义出发,我们可以知道,若一颗树是最优二叉树,则wpl是一定
通过从小到大建树,我们可以计算出该树的wpl
而计算题目给出编码方式的树的wpl,即每个叶子节点 出现频率 * 长度 之和
在这里插入图片描述
同时,对于每个编码,都不允许是其他编码的前缀

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

const int N=70;

int  f[N];

int n;
int wpl;

bool deal(string a, string b){
    int i=0;
    for( ; i<a.size(); i++)
        if(a[i]!=b[i]) break;
    
    // 全部匹配
    if( i==a.size() ) return 1;
    return 0;
}

int solve(){
    string str[N];
    // 首先计算wpl
    int s=0;
    for(int i=1; i<=n; i++){
        string tch;
        cin>>tch>>str[i];
        s+=f[i]*str[i].size();
    }
    if(s!=wpl) return 0;

    // 判断是否是前缀
    for(int i=1; i<=n-1; i++)
        for(int j=i+1; j<=n; j++){
            string a=str[i];
            string b=str[j];
            if(a>b) swap(a, b);
            // 如果 a 是 b 的前缀
            if( deal(a, b) ) return 0;
        }

    return 1;
}

int main(){
    scanf("%d", &n);
    
    // 小根堆
    priority_queue<int, vector<int>, greater<int>> q;
    
    for(int i=1; i<=n; i++){
        // 这个输入没任何用
        char tch[2]; scanf("%s", tch);
        scanf("%d", &f[i]);
        q.push(f[i]);
    }

    // wpl
    while(q.size()>=2){
        int a=q.top(); q.pop();
        int b=q.top(); q.pop();
        wpl+=a+b;
        q.push(a+b);
    }
    
    int m;
    scanf("%d", &m);
    for(int i=1; i<=m; i++)
        if( solve() )
            puts("Yes");
        else   
            puts("No");
    
    return 0;
}

1.愿天下有情人都是失散多年的兄妹

在这里插入图片描述

**题目,我就想知道样例中1号到底是男是女???

首先,存所有人的信息,因为ID是5位数字,且每人不同,因此我们可以用1e6大小的数组存
同时,存信息的时候,需要同时将父母的性别存入(如果没存的话)
值得注意,在这道题中,性别信息以直接给出的为准,而不是以 “父亲ID” 为准,换言之,“父亲ID”不一定对应男,“母亲ID”不一定对应女
在对两人进行判断时,首先判断是否同性
在判断是否近亲时,将其 id代数 作为队列中的元素,直接宽搜或者深搜就ok
一个细节,第五代的父母不需要加入队列,但第五代需要判重

#define fst first
#define sed second

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

typedef pair<int, int> PII;

const int N=1e6+10;

struct node{
    char sex; // 性别
    int fa;   // 父亲id
    int mo;   // 母亲id
};

// 所有人的信息
node arr[N];
int used[N]; 

int deal(int a, int b){
    // Never Mind
    if(arr[a].sex==arr[b].sex) return 1;

    memset(used, 0, sizeof used);
    
    // id 代数 
    queue<PII> q;
    
    // 第一代
    used[a]++, used[b]++;
    q.push({a, 1}), q.push({b,1});
    
    // 找五代祖宗
    while(q.size()){
        auto t=q.front();
        q.pop();

        int moid=arr[ t.fst ].mo; // 这个人妈的id
        int faid=arr[ t.fst ].fa; // 这个人爸的id
        int dai =t.sed+1;           // 爸妈的代数

        if( moid!=-1 ){ // 继续找妈
            if(dai<=4) q.push({moid, dai}); // 第五代不用继续找妈了
            used[ moid ]++;
        }
        if( faid!=-1 ){ // 继续找爸
            if(dai<=4) q.push({faid, dai});
            used[ faid ]++;
        }
        
        // 五代内
        if(used[ moid ]>=2 || used[ faid ]>=2 )
            return 2;
    }

    return 3;
}

int main(){
    // 因为存在爸妈到顶了,预先所有位置的爸妈为-1
    for(int i=0; i<N; i++) arr[i].mo=-1, arr[i].fa=-1, arr[i].sex='s';

    int n;
    cin>>n;
    for(int i=1; i<=n; i++){
        int id;
        cin>>id;
        getchar(); // 读一个空格
        scanf("%c%d%d", &arr[id].sex, &arr[id].fa, &arr[id].mo); 
        
        if( arr[id].fa!=-1 && arr[ arr[id].fa ].sex=='s') arr[ arr[id].fa ].sex='F';
        if( arr[id].mo!=-1 && arr[ arr[id].mo ].sex=='s') arr[ arr[id].mo ].sex='M';
    }
    
    int k;
    cin>>k;
    for(int i=1; i<=k; i++){
        int a, b;
        scanf("%d%d", &a, &b);
        int flg=deal(a, b);
        
        if(flg==1) puts("Never Mind");
        if(flg==2) puts("No");
        if(flg==3) puts("Yes");
    }
    return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骄骄是骄傲的骄骄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值