程序员进阶之算法练习(六十八)

题目1

题目链接
题目大意:
有n个球,序号分别是1、2、3、、、、n,每个球上面有一个数字a[1]、a[2]、a[3]、、、a[n],这组数字是不递减的,即是 a[i]≤a[i+1], 1≤i<;
现在需要给n个球染色,需要满足:
1、每个球只有一个颜色;
2、将某个颜色的球挑选出来,按照序号从小到大放好,上面的数字是严格递增;

问,最少需要几个颜色。

输入:
第一行是,表示样例数 (1≤≤100)
每个样例两行,第一行是整数 (1≤≤100)
第二行是n个整数 1,2,…, (1≤≤)

输出:
每个样例一行,输出最少需要的颜色数量。

Examples
input
5
6
1 1 1 2 3 4
5
1 1 2 2 3
4
2 2 2 2
3
1 2 3
1
1

output
3
2
4
1
1

题目解析:
由于本身数字就是不递减,要满足严格递增,只需要关注数字相同的部分;
相同数字的最大连续长度,就是需要的颜色数量。


int a[N];

int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        int ans = 1, cur = 1;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
            if (i > 0) {
                if (a[i] == a[i - 1]) {
                    ++cur;
                    ans = max(ans, cur);
                }
                else {
                    cur = 1;
                }
            }
        }
        cout << ans << endl;
    }   
    
    return 0;
}

题目2

题目链接
题目大意:
小明喜欢0到9中的一个数字d,如果某个整数的十进制表示中,数字d只出现一次则称这个整数是lucky数;
比如说d=7的时候,17就是lucky数,77就不是lucky数;
现在给出q个整数,问给出的整数是否都能拆分为若干个lucky数之和;

输入:
第一行是样例数 (1≤≤9)
每个样例两行,第一行 and (1≤≤1e4, 1≤≤9).
第二行是个整数 1,2,…, (1≤≤1e9).
输出:
每个样例有q行,每一行对应a[i]是否可以拆分为若干个lucky数之和;

Examples
input
2
3 7
24 25 27
10 7
51 52 53 54 55 56 57 58 59 60

output
YES
NO
YES
YES
YES
NO
YES
YES
YES
YES
YES
YES
NO

题目解析:
以7为例,如果数字大于77,那么必然有解:可以拆分为7x+若干个7的组合;
比如说77=70+7,100=7+7+7+7+72;

那么小于100的数字可以枚举,更大的数字必然有解。

int a[100][10]; // a[i][j]表示数字为i,幸运数字是j,是否有解

int main(int argc, const char * argv[]) {
    // insert code here...

    for (int i = 1; i < 100; ++i) {
        for (int lucky = 1; lucky <= 9; ++lucky) {
            bool ok = 0;
            int tmp = i;
            while (tmp) {
                if (tmp % 10 == lucky) {
                    ok = 1;
                    break;
                }
                tmp /= 10;
            }
            a[i][lucky] = ok;
            for (int k = 1; k <= i; ++k) {
                if (a[k][lucky] && k + lucky < 100) {
                    a[k+lucky][lucky] = 1;
                }
            }
        }
    }
    
    int t;
    cin >> t;
    while (t--) {
        int q, d;
        cin >> q >> d;
        while (q--) {
            int k;
            cin >> k;
            if (k >= 100) {
                cout << "YES" << endl;
            }
            else {
                cout << (a[k][d] ? "YES" : "NO") << endl;
            }
        }
    }   
    
    return 0;
}

题目3

题目链接
题目大意:
n个数字的数组a,可以执行最多k次操作,每次操作是找两个数字,其中一个+1,另外一个-1;
想知道经过最多k次操作之后,数组最小的字典序是什么;

输入:
第一行是样例数 (1≤≤20)
每个样例两行,第一行是整数 and (2≤≤100, 1≤≤10000)
第二行是n个整数 1 , 2, …, (0≤≤100)
输出:
每个样例一行,要求所有数字非负,且 字典序最小;

Examples
input
2
3 1
3 1 4
2 10
1 0

output
2 1 5
0 1

题目解析:
容易知道,要让字典序最小,那么就是让前面的数字尽可能小;
所以从左到右开始选择数字,让前面的数字优先-1,收益最大;
同理,当我们需要+1的时候,为了字典序最小,全部加到最末尾的整数即可;


class Solution {
    static const int N = 100010;
public:
    int n, m;
    int a[N], b[N], c[N], ans[N];
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            cin >> n >> m;
            for (int i = 0; i < n; ++i) {
                cin >> a[i];
            }
            for (int i = 0; i < n - 1; ++i) {
                if (m >= a[i]) {
                    a[n - 1] += a[i];
                    m -= a[i];
                    a[i] = 0;
                }
                else {
                    a[n - 1] += m;
                    a[i] -= m;
                    m = 0;
                }
            }
            for (int i = 0; i < n; ++i) {
                cout << a[i] << " ";
            }
            cout << endl;
        }
    }
}
ac;

题目4

题目链接
题目大意:
有n个整数的数组a,现在将数组分成两个集合,如果两个集合内的整数之和相等,则是不美好的;
现在希望去掉若干个数字,要求n个数字无法拆成两个集合,这两个集合的和是相等的。

输入:
第一行是整数 (2≤≤100)
第二行是n个整数1 , 2, …, (1≤≤2000)
输出:
第一行是需要移除的整数个数m,第二行是m个需要移除整数的下标;

Examples
input
4
6 3 9 12
output
1
2

题目解析:
假设n个数字分成集合a[x]和b[y],并且sum(a) = sum(b),那么sum(a)=sum(n)/2。
最开始的想法是,如果sum(a)=sum(b),那么只要去掉一个集合a或者b中的奇数,那么必然会出现sum(a)!=sum(b); (因为奇数和偶数必定不相同)
问题就变成题目中是否存在一个解,使得sum(a)==sum(b) :
如果有存在,则去掉n个数字中的奇数;
如果不存在,则不需要去掉任何数字;

注意,这里很容易产生误解:觉得去掉最小/最大的整数也是可行的。
例子: 4 4 6 6 8 8
去掉4之后可以拆分为 4 + 6 + 6 = 8 + 8
去掉8之后可以拆分为 4 + 4 + 6 = 6 + 8
直接找一个数字最小、最大都无法直接确定。

我们回到最初判断,我们会首先认为,如果sum(n)是奇数,则无解;
那么假如数组中存在一个奇数,我们只要去掉这个奇数即可。
那如果数组中一个奇数都没有呢?
假如数组都是偶数,假设最终分出来的两个集合a和b,我们对两边的集合除以2,不影响sum(a)=sum(b);
如果还是没有奇数,我们可以继续这样操作。容易知道,这样是一定可以找到一个奇数。
根据上面的思路,我们把每一个数字看成二进制,最右边1出现之后,就是奇数了。那么即是寻找n个数字中,最右边1最早出现的位置。

如果要判断n个数字中,能不能凑出来sum(n)/2的数字,这个不就是背包嘛。

class Solution {
    static const int N = 101, M = 101*2000;
public:
    int n, m;
    int a[N], dp[M], ans[N];
    
    int bitpos(int n) {
        int pos = 0;
        while (n % 2 == 0) {
            n /= 2;
            pos++;
        }
        return pos;
    }
    
public:
    void solve() {
        cin >> n;
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
            sum += a[i];
        }
        
        bool ok = 1;
        if (sum % 2) {
            ok = 0;
        }
        else {
            sum /= 2;
            dp[0] = 1;
            
            for (int i = 0; i < n; ++i) {
                for (int j = sum; j > 0; --j) {
                    if (j >= a[i]) {
                        dp[j] |= dp[j-a[i]];
                    }
                }
            }
            ok = dp[sum];
        }
                
        if (ok) {
            int minIndex = 0, minPos = bitpos(a[0]);
            for (int i = 1; i < n; ++i) {
                if (bitpos(a[i]) < minPos) {
                    minPos = bitpos(a[i]);
                    minIndex = i;
                }
            }
            cout << 1 << " " << minIndex + 1 << endl;
        }
        else {
            cout << 0 << endl;
        }
    }
    
}

题目5

题目链接
题目大意:
有n * k个整数分别为 1、2、3、4、、、n * k,现在需要将这些整数分成n行,并且对于每一行都满足:
任意选择区间[l, r],区间的平均数是整数;
问,是否存在这样的分配方式;

输入:
第一行,整数 表示t个样例 (1≤≤500)
每个样例一行,整数 and (1≤,≤500)

输出:
每个样例,如果无解则输出NO;
如果有解则输出YES,接下来n行分别输出k个数字;

Examples
input
4
1 1
2 2
3 3
3 1

output
YES
1
YES
1 3
2 4
NO
YES
1
2
3

题目解析:
按照题目的要求,对于n行数字,每行数字的任意区间的平均数都是整数;
当区间长度为2时,(a[i][j] + a[i][j+1])/ 2能够整除,那么必须是两个奇数或者两个偶数;
由此我们知道,当k>1的时候,肯定每一行数字都是奇数,或者都是偶数;(n=1或者k=1结果较为简单,这里不做讨论)

那么可以推断出, 如果nk是奇数,那么最终肯定会出现奇数个数字,无法满足要求;
当n
k是偶数时,如果n是奇数,则k是偶数,那么在平均分配奇偶数的时候,必然会在第(n+1)/2行出现奇偶数混杂的情况,无法满足要求;
如果n是偶数,那么就可以按照1、3、5、7、、这样分配所有奇数,2、4、6、8这样分配所有偶数;
任意区间的平均数,都是中间两个数的平均值;

class Solution {
    static const int N = 201010;
    char str[N];
    int dp[N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n, k;
            cin >> n >> k;
            if (k == 1) {
                cout << "YES" << endl;
                for (int i = 0; i < n; ++i) {
                    cout << i + 1 << endl;
                }
            }
            else if (n % 2) {
                cout << "NO" << endl;
            }
            else {
                cout << "YES" << endl;
                for (int i = 0; i < n / 2; ++i) {
                    int tmp = i * k * 2 + 1;
                    for (int j = 0; j < k; ++j) {
                        cout << tmp + j * 2 << " ";
                    }
                    cout << endl;
                    for (int j = 0; j < k; ++j) {
                        cout << tmp + 1 + j * 2 << " ";
                    }
                    cout << endl;
                }
            }
        }
    }
}

你可能感兴趣的:(程序员进阶之算法练习(六十八))