【A】给了一些水果,用这些水果来制作一种食物需要的比例是1:2:4,问制作最多的完整食物需要的水果数目是多少?

首先很容易确定的水果数目就是min(a, b/2, c/4),然后用这个数目乘以7就是我们要的答案了。

【B】通过对样例的观察,我们不难得到解决的方法,我们用两个指针,l,r对于n分奇偶来得到答案,通过观察知道,答案一定是分奇偶排列在上一次得到的序列的左右的,所以我们设置l,r一个较大值,然后最后对答案序列从l到r输出即可。

【C】一个人要从x1到x2,然后电车的初始位置是d,电车给了行驶的方向和车的初始位置,然后给了人和车的速度,最后问人从x1到达x2的最短时间。这个问题就是分类讨论,推公式的问题。再纸上画一下很容易推出来,就不写了。给出我的代码吧

int main()
{
    int ans;
    int s, x1, x2, t1, t2, p, d;
    cin>>s>>x1>>x2>>t1>>t2>>p>>d;
    if(x2 > x1 && d == 1){
        if(p > x1) ans = min((x2-x1)*t2, (2*s+x2-p)*t1);
        else ans = min((x2-x1)*t2, (x2 - p)*t1);
    }
    else if(x2 > x1 && d == -1){
        ans = min((x2-x1)*t2, (p+x2)*t1);
    }
    else if(x2 < x1 && d == -1){
        if(p < x1) ans = min((x1-x2)*t2, (2*s+p-x2)*t1);
        else ans = min((x1-x2)*t2, (p-x2)*t1);
    }
    else{
        ans = min((x1-x2)*t2, (2*s-x2-p)*t1);
    }
    cout<<ans<<endl;
}

【D】题目是要求构造一个序列,这个序列由a个'G'和b个‘B,并且任意连续的字符个数不能超过k个,其中a+b=n,问能否成功构造出这个序列,不能输出“NO”,否则输出你构造的序列。简单贪心,我们直接用k个a和k个b这样的序列,首字母是谁由a和b的大小来决定,我们一直这样怼下去,知道某种字母怼完之后,把另外一种字母直接放到后面就可以了。那么如果存在合法状态的话,我们得到的这个串肯定合法,否则我们只需要检查最后连续的相等字母的个数,判断是不是>k,就可以判断这个串是否合法了。


char s[100010];
int n, k, a, b, cnt;

int main()
{
    cin>>n>>k>>a>>b;
    int mx = max(a, b);
    int mi = min(a, b);
    int cha = mx - mi;
    cnt = 0;
    if(k > n){
        printf("NO\n");
        return 0;
    }
    if(mx == a){
        for(int i = 0; i < min(k-1, cha); i++){
            s[cnt++] = 'G';
        }
        cha -= min(k-1, cha);
        if(mi){
            s[cnt++] = 'G';
            s[cnt++] = 'B';
            mi--;
        }
        for(int i = 0; i < mi; i++){
            for(int j = 0; j < min(k-1, cha); j++){
                s[cnt++] = 'G';
            }
            cha-=min(k-1, cha);
            s[cnt++] = 'G';
            s[cnt++] = 'B';
        }
        while(cnt < n){
            s[cnt] = 'G';
            cnt++;
        }
    }
    else{
        for(int i = 0; i < min(k-1, cha); i++){
            s[cnt++] = 'B';
        }
        cha -= min(k-1, cha);
        if(mi){
            s[cnt++] = 'B';
            s[cnt++] = 'G';
            mi--;
        }
        for(int i = 0; i < mi; i++){
            for(int j = 0; j < min(k-1, cha); j++){
                s[cnt++] = 'B';
            }
            cha-=min(k-1, cha);
            s[cnt++] = 'B';
            s[cnt++] = 'G';
        }
        while(cnt < n){
            s[cnt] = 'B';
            cnt++;
        }
    }
    int cnt2 = 0;
    for(int i = cnt - 1; i >= 0; i--){
        if(s[i] == s[cnt-1]){
            cnt2++;
        }
        else{
            break;
        }
    }
    //cout<<s<<endl;
    if(cnt2 > k)
    {
        printf("NO\n");
    }
    else{
        cout<<s<<endl;
    }
}

【E】现在给了n个数字,然后还有一个1到m,代表可以把a里面任意一个数换成1-m,最后需要保证a数组里面的所有的数的奇数个数等于偶数个数,问最少的换的次数,以及换了之后的数组是什么样子的,当然还有不能达到的情况,输出“-1”。


const int maxn = 2e5+10;
int n, m, a[maxn], num[2], p[2]; //num记录奇偶的个数, p0, p1表示指针
bool flag[maxn]; //记录哪些改变过
set <int> s; //记录不用改变的

int main()
{
    cin>>n>>m;
    memset(flag, true, sizeof(flag));
    for(int i = 1; i <= n; i++){
        cin>>a[i];
        if(s.count(a[i]) == 0){
            num[a[i]&1]++;
            s.insert(a[i]);
        }
        else{
            flag[i] = false;
        }
    }
    int mid = n / 2;
    for(int j = 0; j < 2; j++){
        if(num[j] > mid){
            for(int i = 1; i <= n; i++){
                if(flag[i] && ((a[i] & 1) == j)){
                    flag[i] = false;
                    num[j]--;
                }
                if(num[j] <= mid) break;
            }
        }
    }
    s.clear();
    int ans = 0;
    //把不需要改变的丢进来&&求出不需要改变的个数
    for(int i = 1; i <= n; i++){
        if(flag[i]){
            s.insert(a[i]);
            ans++;
        }
    }
    p[0] = 2, p[1] = 1;
    for(int j = 0; j < 2; j++){
        if(num[j] < mid){
            for(int i = 1; i <= n; i++){
                if(flag[i]) continue;
                while(s.count(p[j]) > 0) p[j] += 2;
                if(p[j] > m){
                    puts("-1");
                    return 0;
                }
                num[j]++;
                a[i] = p[j];
                s.insert(a[i]);
                flag[i] = true;
                if(num[j] >= mid) break;
            }
        }
    }
    cout<<n-ans<<endl;
    for(int i = 1; i <= n; i++) cout<<a[i]<<" ";
}

【F】一个人又k的时间听n首音乐,每一首音乐都有一个愉快度,然后这个人可以选择听这首音乐完整时间t,1/2向上取整的时间,还要满足听了一半的个数<=m。确实没什么想法:贴一个别人的关键解题方法吧:

a. 听的歌曲可以从任意位置开始

b. k时间内听得歌曲都必须听,不能跳过(而这点就限制了,解题思路不能往dp方向考虑,而是区间)

c. 最关键的是:这里固定区间,使得收益最大,(每首歌曲的收益都是正数,没有负数和0,而且听的时间减半也不会对收益造成影响),那么收益最大就是听尽可能多的歌曲,那么,很容易得出结论,(这个题目的限制条件,取半的歌曲的数目上限为w),寻找区间,使得区间内听歌时间最大的w个的歌曲取半,这样得到的是这个区间的最大收益值,这样结果很明确,不容考虑各种组合,不需要枚举,不需要dp,(反正这里也不能dp),最优的也就那么一种。

d. 最终的答案就是遍历所有可能这样区间即可,固定左边界,右边界是非递减的,这就可以使用two pointer来做!

看完之后才觉得恍然大悟,按照官方题解的思路,看着别人的代码写出来,太弱了。


int n, m, k, a[200010], b[200010];
int tt, res, now;
set <pair <int, int> > s1, s2;

int main()
{
    cin >> n >> m >> k;
    REP1(i, 0, n) scanf("%d", &a[i]); //please
    REP1(i, 0, n) scanf("%d", &b[i]); //time
    for(int l = 0, r = 0; r < n; ){
        while(tt <= k && r < n){
            if(s1.size() < m){
                s1.insert(MP(b[r], r));
                tt += (b[r] + 1) / 2;
            }
            else if(s1.begin()->first < b[r]){
                tt -= (s1.begin() -> first + 1) / 2;
                tt += s1.begin() -> first;
                tt += (b[r] + 1) / 2;
                s2.insert(*s1.begin());
                s1.erase(*s1.begin());
                s1.insert(MP(b[r], r));
            }
            else{
                s2.insert(MP(b[r], r));
                tt += b[r];
            }
            now += a[r++];
            if(tt <= k) res = max(res, now);
        }
        while(tt > k)
        {
            if(s1.count(MP(b[l], l)) > 0){
                tt -= (b[l] + 1) / 2;
                s1.erase(MP(b[l], l));
                if(!s2.empty()){
                    auto x = *s2.rbegin();
                    tt -= x.first;
                    tt += (x.first + 1) / 2;
                    s1.insert(x);
                    s2.erase(x);
                }
            }
            else{
                tt -= b[l];
                s2.erase(MP(b[l], l));
            }
            now -= a[l++];
            if(tt <= k){
                res = max(res, now);
            }
        }
    }
    cout<<res<<endl;
}

【G】考虑一颗树,如果满足每层深度上有a[i]结点,最多能有多少叶子结点。那么答案很简单,就是对(a[i]-1)求和再加1(每一层的结点都集中在上一层的一个结点上)。同理,我们考虑最少能有多少叶子结点,就是把上一个的答案再减去min(a[i]-1, a[i-1]-1)的求和,就是每一层的结点都尽可能的分散在上一层的结点上,根据这个,那么如果要求有k个叶子节点,k在最大值与最小值之间,就可以生成出这棵树了。生成的方法,和构造起来差不多,就是先求出最大值与k的差T,然后每一层处理时,如果T>0,就使这一层尽可能的分散在上一层结点上,然后T减少一点。直到T为0,之后的层都直接集中在上一层的一个结点即可。


int max_leaf_node, min_leaf_node;
int n, t, k, a[maxn];

int main()
{
    cin >> n >> t >> k;
    REP2(i, 1, t) cin >> a[i];
    max_leaf_node = 0;
    REP1(i, 1, t) max_leaf_node += (a[i] - 1); max_leaf_node += a[t];
    min_leaf_node = max_leaf_node;
    REP2(i, 2, t) min_leaf_node -= min(a[i] - 1, a[i - 1] - 1);
    if(k <= max_leaf_node && k >= min_leaf_node){
        int cha_leaf_node = max_leaf_node - k;
        cout << n << endl;
        for(int i = 2; i <= 1 + a[1]; i++) cout << "1 " << i << endl;
        int tot = 2 + a[1];
        REP2(i, 2, t){
            int t1 = min(a[i-1] - 1, a[i] - 1), t2 = 0;
            while(cha_leaf_node > 0 && t1 > 0){
                if(t2 > 0){
                    cha_leaf_node--;
                    t1--;
                }
                cout << tot - a[i - 1] + t2 << " " << tot + t2 << endl;
                t2++;
            }
            if(cha_leaf_node == 0 || t1 == 0){
                while(t2 < a[i]){
                    cout << tot - a[i - 1] << " " << tot + t2 << endl;
                    t2++;
                }
            }
            tot += a[i];
        }
    }
    else{
        cout << -1 << endl;
    }
}