一个仅包含'0'和'1'的字符串,长度不超过 2000。
所有非空子串的权值和。
10001
8
长度为 2 的子串中,有 2 个"00"的权值是 1。长度为 3 的 3 个子串权值都是 1。长度为 4 的 2 个子串权值都是 1。长度为 5 的 1 个子串权值是 1。总权值之和为 2+3+2+1=8
首先,给定一个字符串 s
, 当小美执行完最少的操作次数后,目标字符串的形式要么是target = "101010..."
,要么是target = "010101..."
。
例如,对于字符串 s = "10001"
,经过一次操作(中间的 0 -> 1)后,目标字符串为 "target = "10101"",这既是上述的第一种目标字符串形式。
因此,我们可以初始化两个和输入字符串长度相同的两种可能目标字符串,对于上例,初始化的目标字符串为 s1 = "10101", s2 = "01010"
,然后将每一个子串分别与s1
和 s2
对比,不同字符的个数既是所需要的操作数,对于例子,与s1
不同字符的个数count1 = 1
, 与s2
不同字符的个数count2 = 4
, 最后取 min(count1, count2)
即是输入字符串s
01翻转所需的最小操作数(权值)。
对于该题,可以将输入字符串s
的每一个非空子串都与可能的目标字符串target
比对求出每个非空字串的权值,然后分别相加既是所有非空子串的权值和。但是使用这种暴力解法的话会超时,因此需要我们进一步思考,简化算法流程。
为便于理解,首先,我们画个表table
来表示输入字符串s = “10001”
的各个非空子串的权值。
1 | 0 | 0 | 0 | 1 | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | |
0 | 0 | 1 | 1 | 1 | |
1 | 1 | 1 | 1 | ||
2 | 1 | 1 | |||
3 | 0 | ||||
4 |
上表中,table(i,j)
表示输入字符s
的子串s[i:j]
的权值,如table(0, 2) = 1
表示s[0:2] = "100"
的权值。
通过观察这张表,可以发现暴力解法存在重复的计算过程,举个例子,比如在计算s
的子串 subs = "1000"
的权值时,我们已经计算过subs
的子串subsubs = "100"
的权值,这就造成了一种计算资源的浪费。如果我们把subsubs = "100"
的权值计算完后保存了下来,这里为1
, 那么,当我们遇到字符串 subs = "1000"
时,只需要比较最后多出来的一位是否与可能的目标字符串相同即可,如果相等,则subs = "1000"
的权值为subsubs = "100"
的权值 +1
,否则等于subsubs = "100"
的权值。
进一步考虑,因为给定字符串s
的目标字符串可能是0101..
或1010...
的形式,我们事先并不知道,因此,我们需要保存字符串s
两种可能目标字符串的权值。
具体做法为:
s
,初始化两种可能的目标字符串s1="1010..."
和s2="0101..."
; s
不同起始点开始包含的子串的可能权值 vector<vector<int>> nums(n, vector<int>(2, 0))
[^1],n=s.length()
; 以上面的表为例,输入为s="10001"
,具体的执行过程为:
s1 = "10101"
, s2= "01010"
; vector<vector<int>> nums(n, vector<int>(2, 0))
0
,s = "10001"; s1 = "10101"; s2 = "01010"; res = 0; /*从上表可以看出, 以0为起始索引的字符字串包括: 10,100,1000,10001*/ for i = 0 to 4 { for j = i to (i+j < n) { /*然后开始与可能目标子串对比*/ if (s[j] != s1[j]) nums[i][0]++; if (s[j] != s2[j]) nums[i][1]++; res += min(nums[i][0], nums[i][1]); } }
在上面的伪代码中,每计算完一个子串的权值,res
需要及时累加权值和,思考一下,如果res += min(nums[i][0], nums[i][1])
放在第一个for
的外面会是什么样的计算结果呢 ?
[^1]: nums[i][0]
计算的是字符串s
第个位置开始的子串对应目标字符串"1010..."
的权值,nums[i][1]
计算的则是对应目标字符串"0101..."
的权值.
#include <iostream> #include <string> #include <vector> using namespace std; int main() { string s; cin >> s; int res = 0, n = s.length(); // s1 101010..., s2 010101... string s1 = "", s2 = ""; vector<vector<int>> nums(n-1, vector<int>(2, 0)); for (int i = 0; i <= n/2; ++i) { s1 += '1'; s1 += '0'; s2 += '0'; s2 += '1'; } for (int i = 0; i < n -1; ++i) { for (int j = i; j < n; ++j) { if (s[j] != s1[j]) nums[i][0]++; if (s[j] != s2[j]) nums[i][1]++; res += min(nums[i][0], nums[i][1]); } } printf("%lld\n", res); } // 64 位输出请用 printf("%lld")
#include <iostream> using namespace std; int main() { /* 思路: 最小操作次数的求法 一个串要么010101要么101010所以最小操作次数就可以通过两次比对来求得 子串的求法,进行一个遍历吧就 n^3的时间复杂度,空间复杂度o(n) 超时! 2.思路 子串的最优解与长度+1子串的最优解有关系吗? 有关系!且结果为最优解或最优解+1 所以先求长度为2的子串的权值再往外拓展 对于子串(i,j)只需要比对(i,j-1)并进行增加操作 比较两个串0101与1010,求出比对最小值即可,随后保存去比较更长子串 二维数组动态规划?行!我保存各个子串的权值,保存完再一遍遍历求和。 需要两个数组,否则不能确保当前匹配的是0开头还是1开头。麻烦 直接用两个变量保存呢? 若能证明:子串(i,j)是否只用考虑子串(i,j-1)而不用考虑子串(i+1,j) 首先其相同部分(i+1,j-1)为固定权值为k; 对相同部分拓展发现无论i,j位置元素如何,再同一比对下最终其权值都会相同,所以可行 n方时间复杂度 */ int res = 0; //获取输入 string str; cin >> str; //由0开始的和由1开始的 int start0,start1; //所有的长度至少为2的子串 for(int i = 0; i <str.size()-1;i++) { start0 = 0,start1 = 0; //对于start0来说2的整数倍的地方应该为0,对于start1来说为1 if(i%2==0) (str[i]=='0')?start1++:start0++; else (str[i]=='0')?start0++:start1++; for(int j = i + 1; j < str.size();j++) { if(j%2==0) (str[j]=='0')?start1++:start0++; else (str[j]=='0')?start0++:start1++; //子串权值求和 res += (start0>start1)?start1:start0; } } cout<<res; return 0; } // 64 位输出请用 printf("%lld")
import java.util.*; public class Main { static boolean isAchieved; public static void main(String[] args) { Scanner sc = new Scanner(System.in); char[] chars = sc.next().toCharArray(); int n = chars.length; int[][][] dp = new int[n][n][2]; int res = 0; dp[0][0][chars[0] - '0'] = 0; dp[0][0][1 - chars[0] + '0'] = 1; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { if (i == 0 && j == 0) continue; int bit = chars[j] - '0'; dp[i][j][bit] = dp[i][j - 1][1 - bit]; dp[i][j][1 - bit] = dp[i][j - 1][bit] + 1; res += Math.min(dp[i][j][bit], dp[i][j][1 - bit]); } } System.out.println(res); } }
strs = input().strip() n = len(strs) res = 0 for i in range(n): # c0表示当前以i开头的字串,变换后第一个字符为0,需要的变换次数 # c1表示当前以i开头的字串,变换后第一个字符为1,需要的变换次数 c0, c1 = 0, 0 for j in range(i, n): if (strs[j] == '1' and (j - i) % 2 == 1)&nbs***bsp;(strs[j] == '0' and (j - i) % 2 == 0): c1 += 1 else: c0 += 1 res += min(c0, c1) print(res)
st = list(map(int, input().strip())) dp = [list(st), [int(i!=1) for i in st]] res = 0 while len(dp[0])>1: dp = [dp[1][:-1], dp[0][:-1]] for idx, i in enumerate(st[-len(dp[0]):]): dp[int(i==0)][idx] += 1 res += sum([min(i, j) for i, j in zip(dp[0], dp[1])]) print(res)
st = list(map(int, input().strip())) dp = [st, list(map(int, map(lambda x: x==0, st)))] addtiton = dp.copy() res = 0 def dp_add(dp1, dp2): return [[x + y for x, y in zip(row1, row2)] for row1, row2 in zip(dp1, dp2)] while len(dp[0])>1: addtiton = [addtiton[0][1:], addtiton[1][1:]] dp = dp_add([dp[1][:-1], dp[0][:-1]], addtiton) res += sum([min(i, j) for i, j in zip(dp[0], dp[1])]) print(res)
import java.util.*; import java.io.*; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static void main(String[] args) throws Exception { BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); String line = bf.readLine(); int res=0, n= line.length(); int[][][] dp = new int[n][n][2];//dp[i][j][m] 数组[i,j]要变成以m结尾的01串,所需的修改次数,m=0/1 for(int i=0;i<n;i++){ //len=1 if(line.charAt(i)=='0'){ dp[i][i][0]=0; dp[i][i][1]=1; } else{ dp[i][i][0]=1; dp[i][i][1]=0; } for(int j=i+1;j<n;j++){ dp[i][j][0]= dp[i][j-1][1] + (line.charAt(j)=='0' ? 0 : 1); dp[i][j][1]= dp[i][j-1][0] + (line.charAt(j)=='0' ? 1 : 0); } } // for(int i=0;i<n;i++){ // for(int j=i;j<n;j++){ // System.out.println("dp["+i+"]["+j+"][0]=" + dp[i][j][0]); // System.out.println("dp["+i+"]["+j+"][1]=" + dp[i][j][1]); // } // } for(int i=0;i<n;i++){ for(int j=i+1;j<n;j++){ int curr = Math.min(dp[i][j][0], dp[i][j][1]); res +=curr; } } System.out.println(res); } }
import java.util.*; import java.io.*; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); public static PrintWriter stdOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))); public static void main(String[] args) throws IOException{ String str = stdIn.readLine().trim(); int len = str.length(); int res = 0; int[] preSum1 = new int[len + 1]; int[] preSum2 = new int[len + 1]; for (int i = 0 ; i < len; i++){ if (i % 2 == 1){ if (str.charAt(i) != '1') preSum1[i + 1]++; else preSum2[i + 1]++; } else { if (str.charAt(i) != '0') preSum1[i + 1]++; else preSum2[i + 1]++; } preSum1[i + 1] = preSum1[i + 1] + preSum1[i]; preSum2[i + 1] = preSum2[i + 1] + preSum2[i]; } for (int i = 2; i <= len; i++){ for (int j = 0; j <= len - i; j++){ int cnt1 = preSum1[j + i] - preSum1[j]; int cnt2 = preSum2[j + i] - preSum2[j]; res += Math.min(cnt1, cnt2); } } stdOut.println(res); stdOut.flush(); } }
package main import ( "bufio" "fmt" "os" ) func main() { sc := bufio.NewScanner(os.Stdin) buf := make([]byte, 0, 64*1024) // 增加能输入的一行的最大字符数量 sc.Buffer(buf, 1024*1024) sc.Scan() s := sc.Text() res := 0 n := len(s) // 101010... for i := 0; i < n-1; i++ { var pre int for j := i; j < n; j++ { switch (j - i) & 1 { case 1: if s[j] == '1' { pre++ } case 0: if s[j] == '0' { pre++ } } res += min(pre, j-i+1-pre) } } fmt.Println(res) } func min(a, b int) int { if a < b { return a } return b }看了@cx_333大佬的思路做出来的,做了一些空间上的优化:
1、定义两个个数组记录如果全部修改为 0101 或 1010,需要修改的位置,1表示需要修改,0表示不需要修改
let s = "0110"; let zeroList = [0,0,1,1]; // => 0101 let oneList = [1,1,0,0]; // => 1010
2、开始计算两个三个字符时需要修改几个字符
let s = "0110"; let newZeroList = [0,1,2]; // => "01","10","01" let newOneList = [2,1,0]; // => "10","01","10"
然后遍历 newZeroList 和 newOneList 中同一个位置下较小的数加和就是当前长度的权值。
例如
zOL = [0,1,2]
nOL = [2,1,0]
=>[0,1,0] => 权值为 0+1+0=1
3、循环第二步直到长度为 s.length
const rl = require("readline").createInterface({ input: process.stdin }); var iter = rl[Symbol.asyncIterator](); const readline = async () => (await iter.next()).value; void async function () { // Write your code here while(line = await readline()){ solu(line); } }() function solu(str) { let list = str.split('').map(_ => _); let len = str.length; let zeroList = []; let oneList = []; let bit = 0; for (let i = 0;i < len;i++) { if (list[i] == bit) { zeroList.push(0); oneList.push(1); } else { zeroList.push(1); oneList.push(0); } bit = 1 - bit; } let newZeroList = []; let newOneList = []; let sum = 0; for (let i = 1;i < len;i++) { newZeroList.push(zeroList[i - 1] + zeroList[i]); newOneList.push(oneList[i - 1] + oneList[i]); } sum += calc(newZeroList, newOneList, len - 1); for (let p = 2;p < len;p++) { for (let i = 0;i < len - p;i++) { newZeroList[i] += zeroList[i + p]; newOneList[i] += oneList[i + p]; } sum += calc(newZeroList, newOneList, len - p); } console.log(sum); } function calc(list1, list2, len) { // console.log(list1, list2, len); let s = 0; for (let i = 0;i < len;i++) { s += Math.min(list1[i], list2[i]); } return s; }
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); char[] s = in.next().toCharArray(); int n = s.length; int ans = 0; for (int i = 0; i < n; ++i) { int[] dp = new int[2]; dp[1] = 1; // 最后一个位置反转了 for (int j = i + 1; j < n; ++j) { if (s[j] == s[j - 1]) { int t = dp[0]; dp[0] = dp[1]; dp[1] = 1 + t; } else { dp[0] = dp[0]; dp[1] = 1 + dp[1]; } ans += Math.min(dp[0], dp[1]); } } System.out.println(ans); } }
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); // 注意 hasNext 和 hasNextLine 的区别 String st = in.nextLine(); char[] ss = st.toCharArray(); int n = ss.length; // "1": 1; "0": 0 // ss[i][j], [i, j] int[][] weight = new int[n][n]; for (int i = 0; i < n; i++) { if (i % 2 == 1) { weight[i][i] = ss[i]=='1' ? 1: 0; } else { weight[i][i] = ss[i]=='0' ? 1: 0; } } int res = 0; for (int i = 2; i <= n; i ++) { for (int j = 0; j + i - 1 < n; j++) { weight[j][j+i-1] = weight[j][i+j-2] + weight[j+i-1][j+i-1]; res += Math.min( i-weight[j][j+i-1], weight[j][j+i-1]); } } System.out.println(res); } }
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); // s = 0: dp[i][j][1] = dp[i][j][0] + 1; dp[i][j][0] = dp[i][j][1]; // s = 1: dp[i][j][0] = dp[i][j][1] + 1; dp[i][j][0] = dp[i][j][0]; String str = in.nextLine(); int len = str.length(); int[][][] dpList = new int[len][len][2]; for (int i = 0; i < len; i++) { if (str.charAt(i) == '0') { dpList[i][i][0] = 0; dpList[i][i][1] = 1; } else { dpList[i][i][0] = 1; dpList[i][i][1] = 0; } } int sum = 0; for (int i = 0; i < len; i++) { for (int j = i + 1 ; j < len; j++) { if (str.charAt(j) == '0') { dpList[i][j][0] = dpList[i][j-1][1]; dpList[i][j][1] = dpList[i][j-1][0] + 1; } else { dpList[i][j][0] = dpList[i][j-1][1] +1 ; dpList[i][j][1] = dpList[i][j-1][0]; } sum += Math.min(dpList[i][j][0], dpList[i][j][1]); } } System.out.println(sum); } }
strs = list(map(int, input())) n = len(strs) res = 0 for i in range(n): # c0表示strs[i:j + 1]字串,以0开头的最少变换次数 # c0表示strs[i:j + 1]字串,以1开头的最少变换次数 c0, c1 = 0, 0 for j in range(i,n): length = j - i - 1 if (not strs[j] and length % 2 == 0)&nbs***bsp;(strs[j] and length % 2 == 1): # 以0开头长度为偶数的交替字符串最后一位必须为1 # 以1开头长度为奇数的交替字符串最后一位必须为0 c0 += 1 else: c1 += 1 print(i,j,strs[i:j + 1],c0,c1) res += min(c0, c1) print(res)
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); String s = in.next(); int sum = getNum(s); System.out.println(sum); } public static int getNum(String s) { int n = s.length(); StringBuffer sb1 = new StringBuffer(); StringBuffer sb2 = new StringBuffer(); //sb1表示101010101...即以1开头的标准01串 //sb2表示010101010...即以0开头的标准01串 for (int i = 0; i < n; i++) { sb1.append((i+1)%2); sb2.append(i%2); } int sum = 0; //dp1[i][j]表示s.substring(i,j+1)这个子串根据sb1.substring(i,j+1)得到的权重值 //dp2[i][j]表示s.substring(i,j+1)这个子串根据sb2.substring(i,j+1)得到的权重值 //s.substring(i,j+1)这个子串的权重值取dp1[i][j]和dp2[i][j]的最小值 /**举例:对于1000这个串,根据1010得到的权重是1;根据0101得到的权重是3;最后权重取最小值为1 */ int dp1[][] = new int[n][n]; int dp2[][] = new int[n][n]; for (int i = 0; i < n ; i++) { for (int j = i ; j < n; j++) { if (j == i ) { dp1[i][j] = s.charAt(i) == sb1.charAt(i) ? dp1[i][j] : 1; dp2[i][j] = s.charAt(i) == sb2.charAt(i) ? dp2[i][j] : 1; sum = sum + Math.min(dp1[i][j], dp2[i][j]); continue; } dp1[i][j] = dp1[i][j - 1]; dp2[i][j] = dp2[i][j - 1]; if (s.charAt(j) != sb1.charAt(j)) { dp1[i][j] ++; } if (s.charAt(j) != sb2.charAt(j)) { dp2[i][j] ++; } sum = sum + Math.min(dp1[i][j], dp2[i][j]); } } return sum; } }