牛客小白月赛112——E智乃的“凑数”题(Easy Version & Hard Version)

题目

智乃的“凑数”题(Easy Version)
智乃的“凑数”题(Hard Version)

题解(Easy版)

题目分析

对于每个行单元格和列单元格的数字相乘再相加,我们可以对这个公式进行化简,最后得到的就是 行和\(\times\)列和。那么题目就相当于要求在一个给定的整数数组 ( \(k_1\), \(k_2\), ..., \(k_n\) ) 中,找出一个元素子集,将其划分成两个部分,使得两部分的元素和分别形成行和和列和,使得它们的乘积等于一个查询值x。如果存在这样的划分,则输出其中一种方案,否则输出 "No"。

换句话说,我们需要判断能否将数组元素划分为两部分,使得:

  1. 其中一部分的元素和为 a,另一部分的元素和为 b,且a \(\times\) b = x。
  2. 我们只需要找到一种可行的划分方案。

解题思路

本题本质上是一个 二维的 0/1 背包问题

  • f[x][y] 表示是否能通过某些元素的组合形成 xy
  • 递推时,如果 f[x-k[i]][y] 为真,则 f[x][y] 也为真;同理,如果 f[x][y-k[i]] 为真,则 f[x][y] 也为真。
  • 我们还需要使用 pre[x][y] 记录转移来源,以便回溯输出具体的方案。

状态转移

  • 初始化 f[0][0] = true,表示空集合可以形成 0,0 这个状态。
  • 枚举每个元素 k[i],尝试将它放入两个集合之一:
    • 如果 x >= k[i] 并且 f[x-k[i]][y] = true,则 f[x][y] = true,且 pre[x][y] = k[i],表示 xx-k[i] 变过来。
    • 如果 y >= k[i] 并且 f[x][y-k[i]] = true,则 f[x][y] = true,且 pre[x][y] = -k[i],表示 yy-k[i] 变过来。

查询部分

  1. 对于每个查询值 x,我们要找到两个正整数 a, b 使得 a × b = x
  2. 枚举 a,让 b = x / a,并检查 f[a][b] 是否为 true
  3. 如果存在 f[a][b],则回溯输出 ab 是如何由数组 k 组合得到的。

复杂度分析

  • 预处理 f[x][y] 使用 二维背包动态规划,时间复杂度为 O(n \(\times\) 100 \(\times\) 100)。
  • 每次查询需要遍历 sqrt(x) 个因子,每个因子最多回溯 n 次,时间复杂度为 O(\(\sqrt{x}\) \(\times\) n)。
  • 由于 n, m <= 100,最坏情况下可达 O(106),是可以接受的。

总结

  • 该题本质上是 0/1 背包问题的变形,需要使用 二维动态规划 记录可行的 (a, b) 组合。
  • 预处理 f[x][y] 后,可以 快速查询 是否存在符合 a × b = x 的划分方案,并 回溯输出方案
  • 在n,m <= 100范围内能高效运行,但是对于hard就不能运行了。

参考代码(Easy版)

#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int N = 110;
int n, m;
int k[N],pre[N][N];
bool f[N][N];
void query(int n){   
    for(int i = 1; i <= sqrt(n); i ++){
        if(n % i) continue;
        int x = i, y = n / i;
        if(f[x][y]){
            puts("Yes");
            vector<int> vx,vy;
            while(x || y){
                if(pre[x][y] > 0){
                    vx.emplace_back(pre[x][y]);                
                    x -= pre[x][y];
                }else{
                    vy.emplace_back(-pre[x][y]);
                    y += pre[x][y];
                }
            }
            cout << vx.size() << " " << vy.size() << endl;
            for(auto x : vx) cout << x << " ";
            puts("");
            for(auto y : vy) cout << y << " ";
            puts("");
            return;
        }
    }
    puts("No");
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> k[i];
    f[0][0] = true;
    for(int i = 1; i <= n; i ++){
        for(int x = 100; x >= 0; x --){
            for(int y = 100; y >= 0; y --){
                if(f[x][y]) continue;
                if(x >= k[i] && f[x - k[i]][y]){
                    f[x][y] = true;
                    pre[x][y] = k[i];
                }else if(y >= k[i] && f[x][y - k[i]]){
                    f[x][y] = true;
                    pre[x][y] = -k[i];
                }
            }
        }
    }
    while(m --){
        int x;
        cin >> x;
        query(x);
    }
    return 0;
}

题解(Hard版)

hard中数据较大,可以使用一维去映射二维数组,那样子空间只要二维的一半,牛客的空间优化较好,二维状态下也能过。

参考代码(Hard版)

#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int N = 10010;
int n, m;
int k[N],pre[N][N];
bool f[N][N];
void query(int n){   
    for(int i = 1; i <= sqrt(n); i ++){
        if(n % i) continue;
        int x = i, y = n / i;
        if(f[x][y]){
            puts("Yes");
            vector<int> vx,vy;
            while(x || y){
                if(pre[x][y] > 0){
                    vx.emplace_back(pre[x][y]);                
                    x -= pre[x][y];
                }else{
                    vy.emplace_back(-pre[x][y]);
                    y += pre[x][y];
                }
            }
            cout << vx.size() << " " << vy.size() << endl;
            for(auto x : vx) cout << x << " ";
            puts("");
            for(auto y : vy) cout << y << " ";
            puts("");
            return;
        }
    }
    puts("No");
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> k[i];
    f[0][0] = true;
    for(int i = 1; i <= n; i ++){
        for(int x = 10000; x >= 0; x --){
            for(int y = (x?10000/x:10000); y >= 0; y --){
                if(f[x][y]) continue;
                if(x >= k[i] && f[x - k[i]][y]){
                    f[x][y] = true;
                    pre[x][y] = k[i];
                }else if(y >= k[i] && f[x][y - k[i]]){
                    f[x][y] = true;
                    pre[x][y] = -k[i];
                }
            }
        }
    }
    while(m --){
        int x;
        cin >> x;
        query(x);
    }
    return 0;
}
posted @ 2025-03-25 16:30  PZnwbh  阅读(41)  评论(0)    收藏  举报