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

2022-02-13  本文已影响0人  落影loyinglin

正文

题目1

题目链接
题目大意:
输入两个整数a和b,每次操作可以使得a=a+1;
问最少要几次操作,可以使得a可以整除b;

输入:
第一行整数t,表示样例个数; (1≤𝑡≤10000)
接下来t个样例,每个样例一行,有两个整数a、b;(1≤a,b≤10^9)

输出:
最少操作次数;

Examples
input
5
10 4
13 9
100 13
123 456
92 46

output
2
5
4
333
0

题目解析:
为了便于描述,下面用x和y来代替a和b;
如果x<=y,那么操作数就是y-x;
如果x>y,那么可以直接判断x%y==0,不满足则++x;
但是如果这样直接算,这个过程比较慢。在x%y!=0,不断x=x+1的时候,可以确定x/y的结果就是(x+y)/y取整;
那么最小操作次数就是(x + y) / y * y - x


int a[N];

int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        int x, y;
        cin >> x >> y;
        if (x <= y) {
            cout << y - x << endl;
        }
        else {
            if (x % y == 0) {
                cout << 0 << endl;
            }
            else {
                cout << (x + y) / y * y - x << endl;
            }
        }
    }
    
    return 0;
}

题目2

题目链接
题目大意:
有一个长度为n的字符串,有n-2个字符a,有2个字符b;
把这个字符串重新排列,可以若干个字符串,然后按字典序排列,比如说n=5的时候:
aaabb
aabab
aabba
abaab
ababa
abbaa
baaab
baaba
babaa
bbaaa

现在想知道长度为n的字符串重新排列后,第k个字符串是什么;

输入:
第一行整数t,表示样例个数; (1≤𝑡≤10000)
接下来t个样例,每个样例一行,有两个整数 𝑛 and 𝑘 (3≤𝑛≤105,1≤𝑘≤min(2⋅109, 𝑛⋅(𝑛−1)/2)

输出:
最少操作次数;

Examples
input
7
5 1
5 2
5 8
5 10
3 1
3 2
20 100

output
aaabb
aabab
baaba
bbaaa
abb
bab
aaaaabaaaaabaaaaaaaa

题目解析:
从n=5样例,我们只看字符b,会从最后面开始,逐渐往前面排;
可以总结出规律,第1个b往前面挪动位置时,分别有:1、2、3、...、n-1种可能;
那么累计这个数字和sum,直到数字sum大于k,此时找到b的第1个位置;
接下来用sum和k的差值,就可以计算第2个b的位置,剩下的字符就全部是a了;


int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        for (int i = 1; i <= n; ++i) {
            if (k > i) {
                k -= i;
            }
            else {
                int x = n - i - 1, y = n - k;
                for (int j = 0; j < n; ++j) {
                    if (j == x || j == y) {
                        putchar('b');
                    }
                    else {
                        putchar('a');
                    }
                }
                puts("");
                
                break;
            }
        }
    }
    
    return 0;
}

题目3

题目链接
题目大意:
一个字符串,如果没有连续两个字符是相同的,则称为美丽的字符串;

给出一个字符串s,包括a/b/c/?,四种字符;
现在需要把每个?字符,分别填入a/b/c字符中的一个;

输入:
第一行 𝑡,表示样例数 (1≤𝑡≤1000)
每个样例一行,字符串𝑠 包括 'a', 'b', 'c' 和 '?'四种字符,字符串长度不超过10e5。

输出:
如果有解,则输出填充过后的字符串;
如果无解,则输出-1;

input
3
a???cb
a??bbc
a?b?c

output
ababcb
-1
acbac

题目解析:
首先是可以通过动态规划来解决,dp[i][j]=0/1表示第i个字符为a、b、c(j=0/1/2)是否有解;
当s[i]=='?'时,则可以生成d[i][0]、d[i][1]、d[i][2]三种状态,状态转移方程比较直接。

但是,题目还有另外一种解法:
对于字符串中的?字符,假如某个位置只有一个?连续,则只需考虑相邻的字符,填入一个不冲突的字符填入即可;
如果有多个?连续,因为连续?的相邻字符最多只有两种,但是?可以填入三种字符,则?必然可以填入一个字符,使得三个字符不连续相同;

那么,什么情况下会出现-1的情况?
幸好题目给出了样例:原来的字符就有相同字符的情况。

char a[N];



int main(int argc, const char * argv[]) {
    // insert code here...
    int t;
    cin >> t;
    while (t--) {
        scanf("%s", a);
        int n = (int)strlen(a);
        
        for (int i = 0; i < n; ++i) {
            if (a[i] == '?') {
                bool vis[3] = {0};
                if (i - 1 >= 0) {
                    vis[a[i-1] - 'a'] = 1;
                }
                if (i + 1 < n && a[i + 1] != '?') {
                    vis[a[i+1] - 'a'] = 1;
                }
                for (int j = 0; j < 3; ++j) {
                    if (!vis[j]) {
                        a[i] = 'a' + j;
                    }
                }
            }
        }
        
        bool ok = 1;
        for (int i = 1; i < n; ++i) {
            if (a[i] == a[i - 1]) {
                ok = 0;
            }
        }
        if (ok) {
            printf("%s\n", a);
        }
        else {
            puts("-1");
        }
    }
           
    
    return 0;
}

题目4

题目链接
题目大意:
给出1~n的n个整数组成的数组,每个数字都只有一个;
我们把数字i称为beautiful,如果能够在数组中找到一段连续的数字,这个区间内的数字是1到i;
比如说对于数组[4,5,1,3,2,6],我们能找到:
[1], [1,3,2], [4,5,1,3,2],[4,5,1,3,2,6] 这四个区间,所以数字1、3、5、6是beautiful,数字2、4不是beautiful;

现在给出一个1~n组成的数组,问数组中有哪些数字是beautiful;

输入:
第一行 𝑡 (1≤𝑡≤1000) 表示接下来有t个样例
每个样例的第一行是𝑛 (1≤𝑛≤2⋅10^5),表示数组的长度;
接下来一行是n个整数;

输出:
每一个样例输出一行长度为n的字符串,每个字符都是01组成,第i个字符为1表示第i个数字是beautiful;

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

output
101011
11111
1001

题目解析:

看到题目的第一想法是从小到大排序,然后从1开始枚举数字;
对于某个数字x,如果左边数字比x小,则合并到集合x;
如果右边的数字比x小,则合并到集合x;
这样不断枚举,不断合并,通过集合的元素和x是否相同,就可以判断是否存在解。(对应下面解法A)

更优解:
对于数字k是否有解,其实是看[1, k]这个区间内,所有数字的左边界和右边界的距离,是否刚好等于数字k;
比如说k=3,[1,2,3]三个数字的左边界是3(对应1的位置),右边界是5(对应2的位置),距离是 (5-3+1)=3,所以k=3有解;
比如说k=4, [1,2,3,4]四个数字的左边界是1(对应4的位置),右边界是5(对应2的位置),距离是 (5-1+1)=5≠k,所以k=4无解。(对应下面解法B)



class SolutionA {
    int a[N];
    int fat[N];
    int cnt[N];
    
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
    
    
    int find(int x) {
        if (fat[x] == x) {
            return x;
        }
        int ret = find(fat[x]);
        if (ret != fat[x]) {
            fat[x] = ret;
            cnt[ret] += cnt[x];
            cnt[x] = 0;
        }
        
        return fat[x];
    }

    void merge(int x, int y) {
        int fx = find(x), fy = find(y);
        if (fx > fy) {
            fat[fx] = fy;
            cnt[fy] += cnt[fx];
            cnt[fx] = 0;
        }
        else {
            fat[fy] = fx;
            cnt[fx] += cnt[fy];
            cnt[fy] = 0;
        }
        find(x);
        find(y);
    }
    
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            scanf("%d", &n);
            
            for (int i = 0; i < n; ++i) {
                scanf("%d", &a[i]);
            }
            
            while (!q.empty()) {
                q.pop();
            }
            
            memset(fat, 0, sizeof(fat));
            memset(cnt, 0, sizeof(cnt));
            
            for (int i = 0; i < n; ++i) {
                q.push(make_pair(a[i], i));
            }
            
            while (!q.empty()) {
                pair<int, int> tmp = q.top();
                tmp = q.top();
                q.pop();
                
                fat[tmp.first] = tmp.first;
                cnt[tmp.first] = 1;
                
                int l = tmp.second - 1;
                if (l >= 0 && a[l] < tmp.first) {
                    merge(a[l], tmp.first);
                }
                int r = tmp.second + 1;
                if (r < n && a[r] < tmp.first) {
                    merge(a[r], tmp.first);
                }
                
                if (cnt[find(tmp.first)] == tmp.first) {
                    putchar('1');
                }
                else {
                    putchar('0');
                }
            }
            puts("");
        }
    }
}ac;


class SolutionB {
    int a[N];
    
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            scanf("%d", &n);
            
            for (int i = 0; i < n; ++i) {
                int x;
                scanf("%d", &x);
                a[x-1] = i;
            }
            
            int left = n, right = 0;
            for (int i = 0; i < n; ++i) {
                left = min(left, a[i]);
                right = max(right, a[i]);
                if (right - left == i) {
                    cout << "1";
                }
                else {
                    cout << "0";
                }
            }
            cout << endl;
        }
    }
}ac_fast;

题目5

题目链接
题目大意:
举办一场比赛,有n个人参加比赛,并得到了n个分数p[i];
现在需要分配金银铜牌的数量分别为g、s、b个,有以下要求:
金银铜的数量必须有一个;(𝑔>0 , 𝑠>0 and 𝑏>0))
金牌人数要少于银牌和铜牌;(𝑔<𝑠 and 𝑔<𝑏)
金银铜的分数,必须严格减少,金牌分数>银牌分数>铜牌分数>没得奖的分数;
获奖人数不超过总人数的一半;( 𝑔+𝑠+𝑏≤n/2)

输入:
第一行𝑡,表示有t个样例 (1≤𝑡≤10000)
每个样例有两行
第一行 𝑛 (1≤𝑛≤4⋅1e5)
第二行 𝑝1,𝑝2,…,𝑝𝑛 (0≤𝑝𝑖≤1e6, 𝑝1≥𝑝2≥⋯≥𝑝𝑛 )

输出:
每个样例输出一行,三个整数𝑔,𝑠,𝑏;
g=s=b=0表示无法分配;
否则输出g、s、b分别表示金银铜牌的数量。

input
5
12
5 4 4 3 2 2 1 1 1 1 1 1
4
4 3 2 1
1
1000000
20
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
32
64 64 63 58 58 58 58 58 37 37 37 37 34 34 28 28 28 28 28 28 24 24 19 17 17 17 17 16 16 16 16 11

output
1 2 3
0 0 0
0 0 0
2 5 3
2 6 6

题目解析:
总结一下题目的意思:
金银铜铁,共需要4个分数(铁牌表示无得奖);
金牌人数要少于银牌和铜牌;
金银铜加起来不超过一半;
希望获奖的人尽可能多;

将数字去重,按照数字大小排序,每个数字有一个权值,权值就是人数;
相当于把数组切分成a、b、c、d共4段,要求sum(a)<sum(b) & sum(a)<sum(b),并且sum(a+b+c)尽可能的大,又不超过权值总和的1/2;

假设新的数组排序之后是s[1~m],金牌只需要有最少的人数,那么就是s[1];
从题目的要求来分析,s[1]是分给金牌,s[2~x]分给银牌,x的求法就是直接遍历,累计银牌人数大于金牌;
同理,铜牌的人数也要大于金牌,然后就可以不断遍历,只要满足s[1], s[2], s[3] 之和小于n/2;
剩下所有人分给铁牌。

map<int, int> h;
vector<int> s;

int main(int argc, const char * argv[]) {
    // insert code here...
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        
        h.clear();
        while (!s.empty()) {
            s.pop_back();
        }
        
        for (int i = 0; i < n; ++i) {
            int k;
            scanf("%d", &k);
            ++h[k];
        }
        for (map<int, int>::iterator iter = h.begin(); iter != h.end(); ++iter) {
//            cout << iter->first << " " << iter->second << endl;
            s.push_back(iter->second);
        }
        
        int ans[3] = {0};
        
        if (s.size() >= 4) {
            int x = s.back();
            s.pop_back();
            
            int y = 0;
            while (x >= y && !s.empty()) {
                y += s.back();
                s.pop_back();
            }
         
            int z = 0;
            while (x >= z && !s.empty()) {
                z += s.back();
                s.pop_back();
            }
            
            while (x + y + z <= n / 2) {
                ans[0] = x;
                ans[1] = y;
                ans[2] = z;
                
                if(s.empty()) {
                    break;
                }
                z += s.back();
                s.pop_back();
            }
        }
        
        cout << ans[0] << " " << ans[1] << " " << ans[2] << endl;
    }
           
    
    return 0;
}
上一篇下一篇

猜你喜欢

热点阅读