首页 > 试题广场 >

小苯的魔法染色

[编程题]小苯的魔法染色
  • 热度指数:1087 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
\hspace{15pt}小红面前有一堵长度为 n 的墙,用一个只由 \tt W(白色)和 \tt R(红色)组成的字符串 a_1a_2\dots a_n 表示。她希望最终将整面墙全部染成红色。

\hspace{15pt}为此她请来了魔法师小苯。一次施法的流程如下:
\hspace{23pt}\bullet\,小苯选择一个闭区间 [l,r]\ (1\leqq l\leqq r\leqq n)
\hspace{23pt}\bullet\,立刻将区间内的所有格子染成红色。

\hspace{15pt}小苯至多施法 m 次,且每次施法的区间长度 \left(r-l+1\right) 不得超过 k

\hspace{15pt}现在小苯想知道,将整堵墙染成红色所需的最小 k 是多少。请你求出这个 k 的最小可能值。

输入描述:
\hspace{15pt}输入包含两行。

\hspace{15pt}第一行输入两个正整数 n,m\ \left(1\leqq m\leqq n\leqq 2\times10^5\right)——墙的长度与小苯允许施法的最大次数。

\hspace{15pt}第二行输入一个长度为 n 的字符串 s,保证 s 仅由字符 \tt W\tt R 组成。


输出描述:
\hspace{15pt}输出一个正整数,表示满足要求的最小 k
示例1

输入

5 2
WRWWR

输出

2

说明

小苯可以进行 m = 2 次操作,每次操作的长度必须在 2 以内。
一种可能的染色方式是:选择 [1, 2] 再选择 [3,4],操作后整面墙都会被染红,可以证明不存在单次操作比 2 更小的长度。
推荐
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define inf 1e18
typedef long long ll;
#define int long long


int n, m;
string s;

bool check(int mid) {
    vector<int> dp(n + 1, 1e18);
    dp[0] = 0;
    for(int i = 1; i <= n; i++) {
        if(s[i] == 'R') {
            dp[i] = dp[i - 1];
        }
        else {
            dp[i] = dp[max(0LL, i - mid)] + 1;
        }
    }
    return dp[n] <= m;
}

void solve() {
    cin >> n >> m >> s;
    s = " " + s;
    int l = 0, r = n;
    while(l < r) {
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
}


/*	

5 2
WRWWR
2

3 1
RRR

3 3
WWW

*/

signed main () {
//     init(minp, primes, m); // primes
    // init();
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int _ = 1;
//   cin >> _;
    while(_ -- ) {
        solve();
    }
    return 0;
}

编辑于 2025-02-18 17:23:21 回复(0)
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n=in.nextInt(),m=in.nextInt();
        TreeSet<Integer> set=new TreeSet<>();
        in.nextLine();
        String wall=in.nextLine();
        for(int i=0;i<n;i++) {
            if(wall.charAt(i)=='W') set.add(i);
        }
        if(set.ceiling(0)==null) {
            System.out.print(0);
            return;
        }
        int left=-1,right=n+1;
        while(left+1<right) {
            int mid=(left+right)/2;
            if(check(set,mid,m)) right=mid;
            else left=mid;
        }
        System.out.print(right);
    }
    public static boolean check(TreeSet<Integer> set,int k,int m) {
        int idx=set.ceiling(0);
        Integer temp;
        for(int i=0;i<m;i++) {
            idx+=k;
            if((temp=set.ceiling(idx))==null) return true;
            idx=temp;
        }
        return false;
    }
}

发表于 2025-02-14 14:31:15 回复(1)
#include <cstddef>
#include <iostream>
using namespace std;

bool check(string& s, int k, int m, int n) {
    for (int i = 0; i < n; i++) {
        if (s[i] == 'W') {
            m--;
            // 上面i++还会有 + 1操作
            i += k - 1;
        }
        if (m < 0) {
            return false;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    int n, m;
    cin >> n >> m;
    string s;
    cin >> s;
    int left = 0, right = n;
    int ans = right;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (check(s, mid, m, n)) {
            ans = mid;
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    cout << ans << endl;
    return 0;
}
// 64 位输出请用 printf("%lld")

发表于 2025-02-28 20:42:39 回复(0)
一开始l设为-1,但是随便找了几个数据测出问题了,RRRRR就错了,思考了一下不能加在check里面,那就得特判一下。
接着下一组:5 2 RRWWR,想差了,把R,W的含义弄混了,导致一直认为答案是2,找了别人的代码还以为被我找到了hack数据,结果测了好几个发现都是1才发现,正确答案是1,耗了这么久
#include<bits/stdc++.h>
#include <iterator>
using namespace std;
#define int long long
#define endl '\n'
#define N 105

//k要最小,必定是施法m次
//二分的x是区间长度,check内部,是W的话需要染色,染完就跳过X个继续遍历
int c[N];
int n,m;
string s;
bool check(int x){
    int cnt=0;
    for(int i=0;i<s.size();){
        // cout<<"i"<<" "<<i<<endl;
        if(s[i]=='W'){
            i+=x;
            cnt++;
        }
        else 
            i++;
    }
    return cnt<=m;
}
void solve() {
    cin>>n>>m;
    cin>>s;
    int l=0,r=2*n;
    //判断是不是全是R
    int f=0;
    for(int i=0;i<s.size();i++)
        if(s[i]=='W')
            f=1;
    if(f==0){
        cout<<0<<endl;
        return ;
    }
    //现在x肯定是不为0
    while(l+1<r){
        int mid=(l+r)/2;
        if(check(mid))
            r=mid;
        else 
            l=mid;
    }
    cout<<r<<endl;
    return ;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int T = 1;
    while (T--)
        solve();
    return 0;
}


发表于 2025-09-30 22:06:46 回复(0)
两次二分查找:
1. 第一层二分查找的内容是:第一个合法的k的值(最小的)
2. 第二层二分查找的内容是:在whitePos(存储白色位置的递增数组)里找到最后一个不超过maxReach的位置
import java.util.*;

public class Main {
    // 判断每次施法的区间长度为k是否可行
    private static boolean canCover(List<Integer> whitePos, int k, int m) {
        int cnt = 0, pos = 0, size = whitePos.size();
        while(pos < size) {
            cnt++;
            if(cnt > m) {
                return false;
            }
            int start = whitePos.get(pos);
            int maxReach = start + k - 1;
            // 二分查找whitePos 找到最后一个不超过maxReach的位置
            int left = pos, right = size - 1;
            int res = pos;
            while(left <= right) {
                int mid = right + (left - right >> 1);
                if(whitePos.get(mid) <= maxReach) {
                    res = mid;
                    left = mid + 1;
                } else {
                    right = mid -1;
                }
            }
            pos = res + 1;
        }
        return cnt <= m;
    }


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.nextInt();
        char[] wall = in.next().toCharArray();
        int ans = 0;
        List<Integer> whitePos = new ArrayList<>();
        for(int i = 0; i < n; i++) {
            if(wall[i] == 'W') {
                whitePos.add(i);
            }
        }

        if(whitePos.isEmpty()) {
            System.out.println(0);
            return;
        }

        int left = 1, right = n;
        while(left <= right) {
            int mid = right + (left -right >> 1);
            if(canCover(whitePos, mid, m)) {
                ans = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        System.out.println(ans);
    }
}


发表于 2025-04-23 17:43:47 回复(0)
import java.util.*;
import java.io.*;

public class Main {
    static int n, m;
    static String s;
    public static void main(String[] args) {
        try(BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
            String[] line = br.readLine().split("\\s+");
            n = Integer.parseInt(line[0]);
            m = Integer.parseInt(line[1]);
            s = br.readLine();
            boolean hasW = false;
            for (int i = 0; i < s.length(); ++i) {
                if (s.charAt(i) == 'W') {
                    hasW = true;
                    break;
                }
            }
            if (!hasW) {
                System.out.println(0);
                return;
            }
            int left = 1, right = n / m + (n % m) + 1;
            while (left < right) {
                int mid = left + ((right - left) >> 1);
                int i = 0, j = m;
                while (i < s.length()) {
                    if (s.charAt(i) == 'R') {
                        ++i;
                    } else {
                        i += mid;
                        --j;
                        if (j < 0) {
                            break;
                        }
                    }
                }
                if (j < 0) {
                    // k要开大一些
                    left = mid + 1;
                } else {
                    // k可以继续缩小
                    right = mid;
                }
            }
            System.out.println(left);
        } catch (IOException ie) {}
    }
}
发表于 2025-04-09 20:27:55 回复(0)
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        in.nextLine();
        String str = in.nextLine();
        for(int k=1;k<=n;k++){
            int tmp = m;
            for(int i=0;i<n;i++){
                char ch = str.charAt(i);
                if(ch == 'W'){
                    if(tmp == 0){
                        break;
                    }
                    tmp--;
                    i = i+k-1;
                }
                if(i >= n-1){
                    if(tmp == m) k = 0;
                    System.out.println(k);
                    return;
                }
            }
        }
    }
}
只会暴力,能过
发表于 2025-03-21 22:12:02 回复(0)
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.nextInt(), left = -1, right = n, last = n;
        String a = in.next();
        // 记录下一个白色块的位置,方便快速搜索
        int[] next = new int[n];
        for (int i=n-1; i>=0; i--) {
            if (a.charAt(i) == 'W') {
                last = i;
            }
            next[i] = last;
        }
        // 二分查找,区间左开右开(left, right)
        while (left + 1 < right) {
            int k = left + ((right - left) >> 1);
            if (check(next, k, n, m)) {
                right = k;
            } else {
                left = k;
            }
        }
        System.out.println(right);
    }

    private static boolean check(int[] next, int k, int n, int m) {
        int cnt = 0, index = 0;
        // next[index]表示从index开始的第一个W的位置
        // 每次染色表示位置+k,将所有包含W的染完的次数不超过m即可行
        while (index < n && next[index] < n) {
            // 找到下一个W的位置
            index = next[index];
            // 从当前的W块开始,染色k个块,继续检查后续
            index += k;
            // 染色次数超过m表示方案不可行
            cnt++;
            if (cnt > m) {
                return false;
            }
        }
        return true;
    }
}

编辑于 2025-03-11 17:39:16 回复(0)
//事实上这种二分题有个小技巧 额外定义一个res用于储存符合条件的结果
//这样就不用纠结是取l还是r了
//由于该种条件已经被res保存 l可以无脑++ r可以无脑-- 去探索其他可能性
//符合条件就更新res 不符合就不更新res 这种限制下结束二分输出的结果一定是符合条件的res
//几乎适用于所有二分

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();//数组长度
        int MaxMagicCount = in.nextInt();
        in.nextLine();
        String s = in.nextLine();
        if (s.contains("W")) {//有白色才染色
            int l = 1;
            int r = (int) 1e6;
            int res = (int) 1e8;//随便定义一个都行
            while (l <= r) {
                boolean AllRed = true;//假设都可以染红
                int CurMagicCount = 0;
                int mid = l + (r - l) / 2;
                //从开头开始尝试染色
                for (int i = 0; i < n; i++) {
                    if (s.charAt(i) == 'W') {
                        //遇到白色使用能力 直接跳到施法后mid-1格 方便循环的i++后的下一个格子是没有检查过的
                        i = i + mid - 1;
                        CurMagicCount++;
                        if (CurMagicCount > MaxMagicCount) {
                            AllRed = false;//提前终止条件为超过最大次数
                            break;
                        }
                    }
                }
                if (AllRed) {
                    res = mid;//都红了说明是个可行解 存储
                    r = mid - 1;//尝试减小范围
                } else {
                    l = mid + 1;//不能全染得增大范围了
                }
            }
            System.out.println(res);
        } else {
            System.out.println(0);//没有白色不用染色了
        }
    }
}

发表于 2025-03-10 22:44:00 回复(0)
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int len = in.nextInt(), act = in.nextInt();
        char[] wall = in.next().toCharArray();
        int maxPW = 0;
        boolean flag = true;
        while (flag) {
            int life = act;
            int step = 0;
            while (step < len) {
                if ('W' == wall[step]) {
                    life--;
                    if (life < 0) {
                        maxPW++;
                        break;
                    }
                    step += maxPW;
                } else step++;
            }
            if(step >= len) flag = false;
        }
        System.out.println(maxPW);
    }
}
吓即儿写的还挺快
发表于 2025-03-07 00:18:42 回复(0)
符合二分查找的原则。
如果 k 满足的话,k + 1 一定满足,故在[1,n] 之间用二分查找最小的k。
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();

        String str = sc.next();

        int ans = solve(n,m,str);

        System.out.println(ans);
        sc.close();
    }

    private static int solve(int n , int m , String str){
        int[] next = new int[n];
        next[n-1] = n;
        for(int i = n - 2 ; i >= 0 ; i--){
            next[i] = str.charAt(i+1) == 'W' ? i + 1 : next[i+1];
        }
        if(next[0] == n){
            return 0;
        }
        int left = 1 , right = n;
        while(left <= right){
            int mid = (left + right) >> 1;
            if(check(str,next,m,mid)){
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }

    private static boolean check(String str,int[] next , int m , int k){
        int pos = str.charAt(0) == 'W' ? 0 : next[0];
        int n = next.length;
        while(m-- > 0){
            int rightBorder = Math.min( n -1,pos + k - 1);
            pos = next[rightBorder];
            if(pos == n){
                return true;
            }
        }
        return false;
    }
}


发表于 2025-03-06 20:17:18 回复(0)
n, m = map(int, input().split())
wall = input()



def can_paint(k):
    count = 0
    i = 0
    while i < n:
        # 找到当前位置右侧最近的白色格子
        j = i
        while j <  n and wall[j] == 'R':
            j += 1
        if j == n:
            break
        end = min(j + k - 1, n - 1)
        i = end + 1
        count += 1
    return count <= m

# 因为最少要涂一块,所以left = 1
left, right = 1, n
while left < right:
    mid = (left + right) // 2
    if can_paint(mid):
        right = mid
    else:
        left = mid + 1

# 检查一下是不是真的不需要涂
wuhu = [char for char in wall if char == 'W']
if wuhu == []:
    print(0)
else:
    print(left)


发表于 2025-03-05 15:06:37 回复(0)
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
in.nextLine();
char[] arr = in.nextLine().toCharArray();
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (check(mid, arr, m)) {
r = mid;
} else {
l = mid + 1;
}
}
System.out.println(r);
}

public static boolean check(int x, char[] arr, int m) {
if (x == 0) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == 'W') return false;
}
return true;
}
int cnt = 0;
for (int i = 0; i < arr.length; ) {
if (arr[i] == 'R') {
i++;
continue;
}
int j = 0;
while (i < arr.length && j < x ) {
j++;
i++;
}
cnt++;
}
return cnt <= m;
}
}
发表于 2025-02-15 16:49:48 回复(0)