牛客周赛
题目:
小红拿到了一个回文串,她希望你将这个回文串重排,使得重排后仍然是回文串且和原串不同。你能帮帮她吗?
思路:
取回文串的第一个字符,向后查找与这个字符不同的字符下标,如果查找到该字符串的一半(字符串为奇数时查找到字符串长度减一的一半)还没有找到不同的字符,则输出-1。如果找到了,就将该位置和他对应的字符分别和第一个字符,最后一个字符交换即可。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | import java.io.*; import java.util.*;
  public class Main {     public static void main(String[] args) throws IOException {         Scanner scan = new Scanner(System.in);         String str = scan.next();         int len = str.length();         int n = len / 2;         if(len % 2 == 1) {             n--;         }         char c = str.charAt(0);         int ind = 0;         for(int i = 1;i <= n; i++) {             if(str.charAt(i) != c) {                 ind = i;             }         }         if(ind == 0) {             System.out.println(-1);             return;         }         char[] chars = str.toCharArray();         chars[0] = chars[ind];         chars[ind] = c;         chars[len - 1] = chars[0];         chars[len - 1 - ind] = chars[ind];         StringBuilder sb = new StringBuilder("");         for(char ch : chars) {             sb.append(ch);         }         System.out.println(sb);     } }
 
 
   | 
题目:
小红拿到了两个正整数x,y,她可以进行以下两种操作:
- 将两个数同时乘以a。
 - 若a既是x的因子,也是y的因子,则将两个数同时除以a。
 
小红希望最终两个数都在[l,r]区间内。她想知道最终的x有多少种不同的取值方案?
思路:
因为可以同时进行1和2两种操作。
设n为x和y的最大公约数,则x可以分解为xt * n,y可以分解为yt * n,此时xt和yt都不能再进行2操作
此时任何x 和 y 进行1操作都得到 a * x和a * y,相当于a * n * xt和a * n * yt。
此时任何x 和 y 进行2操作都得到 xt 和 yt 的倍数。
可以看出,x和y无论进行多少次1和2操作,都会得到xt和yt的倍数。要求[l , r]区间中的可能数,可以求xt * a和yt * a的在区间内有多少种可能性。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | import java.io.*; import java.util.*; public class Main {     public static void main(String[] args) throws IOException {         Scanner scan = new Scanner(System.in);         int x = scan.nextInt();         int y = scan.nextInt();         int l = scan.nextInt();         int r = scan.nextInt();         int max = (int)gcd(x, y);         x = x / max;         y = y / max;         int xl1 = l / x;         if(l % x != 0) xl1 ++;         int xr1 = r / x;         int yl1 = l / y;         if(l % y != 0) yl1++;         int yr1 = r / y;         int res = Math.min(xr1, yr1)-Math.max(xl1, yl1)+1;         System.out.println(res);     }     public static long gcd(long a, long b) {         return (a % b == 0) ? b : gcd(b, a % b);     } }
 
 
   | 
题目:
小红拿到了一棵树,初始所有节点都是白色。
小红希望染红若干个节点,使得不存在两个白色节点相邻。
小红想知道,共有多少种不同的染色方案?
由于答案过大,请对10^9+7取模。
思路:
比赛的时候没有想到用List[]类,构造的时候写错了。如果a,b两个边相邻,就将a加入nums[b]的list中,b加入到nums[a]的list中。这样子nums[i]中存放的就是当前节点的所有相连节点。将第一个节点当成根节点,nums[i]就是下标为i的节点下的所有子节点。因为list中也包含当前节点的父节点,所以在遍历的时候要判断去掉父节点。
用dp的思想,开辟一个二维数组,dp[i][0]存放当前节点染红的方案数,dp[i][1]存放当前节点不染的方案数。可以得到dp[i][0]等于子节点染红+不染的方案数之积,dp[i][1]等于子节点染红的方案数之积。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | import java.io.*; import java.util.*;
  public class Main {     static long[][] dp;     static List<Integer>[] nums;     static int M = (int)(1e9+7);     public static void main(String[] args) throws IOException {         Scanner scan = new Scanner(System.in);         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));         int n = Integer.parseInt(br.readLine());         nums = new List[n + 1];         for(int i = 0; i <= n; i++) {             nums[i] = new ArrayList<>();         }         for(int i = 0; i < n - 1; i++) {             String[] s = br.readLine().split(" ");             int a = Integer.parseInt(s[0]);             int b = Integer.parseInt(s[1]);             nums[a].add(b);             nums[b].add(a);         }         dp = new long[n + 1][2];         dfs(1, -1);         bw.write((dp[1][0] + dp[1][1]) % M + "");         bw.flush();         br.close();         bw.close();     }     public static void dfs(int cur, int father) {         dp[cur][0] = 1;         dp[cur][1] = 1;         List<Integer> lists = nums[cur];         for(int i : lists) {             if(i != father) {                 dfs(i, cur);                 dp[cur][0] = ((dp[i][0] + dp[i][1]) * dp[cur][0]) % M;                 dp[cur][1] = (dp[i][0] * dp[cur][1]) % M;             }         }     } }
 
 
   | 
DIV2
思路:
要将x分成n份,并且该n份的最大公约数最大,每一份可以写成a1 * m, a2 * m, a3 * m,……,an * m。
可以看出,m也是x的最大公约数,x=(a1+a2+a3+…+an)* m。
如果想要使m最大,那就要使(a1+a2+a3+…+an)尽可能小,所以要求x中大于 n 最小的约数。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | import java.io.*; import java.util.*;   public class Main {     public static void main(String[] args) throws IOException {         Scanner scan = new Scanner(System.in);         int t = scan.nextInt();         while (t-- > 0) {             int x = scan.nextInt();             int tr = x;             int n = scan.nextInt();             List<Integer> list = new ArrayList<>();             for(int i = 2; i <= x/i; i++) {                 while (x % i == 0) {                     list.add(i);                     x = x / i;                 }             }             if(x != 1) list.add(x);             int len = list.size();             int[] dp=new int[len +1];             dp[0] = 1;             for(int i = 1; i <= len; i++) {                 dp[i] = list.get(i - 1);             }             int val = get(list, 0, n, 0, 1);             System.out.println(tr/val);         }     }     public static int get(List<Integer> list,int ind, int n, int min, int sum) {         if(min != 0 && sum <= min && sum >= n) {             return sum;         }         if(min != 0&& sum >= n) {             return min;         }         if(min == 0 && sum >= n) {             return sum;         }         for(int i = ind; i < list.size(); i++) {             min = get(list, i + 1, n, min, sum * list.get(i));         }         return min;     } }
   | 
思路:
要判断字符串是否包含前k个字母组成的所有长度为n的字符串,将k个字母全部出现一次算成一遍,统计一共出现过几遍。如果没有达到n遍,那么就输出NO。
要找反例,每次统计的时候将每一遍最后出现的那个字符记录下来,这样子就保证反例下一个字母是在下一遍中出现的。因为统计的遍数没有达到n,所以当记录到最后已经记录的字符数量肯定是小于n的,而将字符串剩下的字符中没有出现的字母记录下来,只要这个字母在前面已经记录过的字母后面出现,这时字符串就不能构成这样子的序列。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | import java.io.*; import java.util.*;
  public class Main {     public static void main(String[] args) throws IOException {         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));         int t = Integer.parseInt(br.readLine());         while (t-- > 0) {             String[] s = br.readLine().split(" ");             int n = Integer.parseInt(s[0]);             int k = Integer.parseInt(s[1]);             int m = Integer.parseInt(s[2]);             String str = br.readLine();             char[] chars = str.toCharArray();             int[] nums = new int[k];             int cur = 0;             int count = 0;             StringBuilder sb = new StringBuilder("");             for(int i = 0; i < m; i++) {                 int ind = chars[i] - 'a';                 if(ind < k && nums[ind] == cur) {                     nums[ind]++;                     count++;                     if(count == k) {                         sb.append(chars[i]);                         cur++;                         count = 0;                     }                 }             }             if(cur >= n) {                 bw.write("YES\n");                 continue;             }             bw.write("NO\n");             char c = 'a';             for(int i = 0; i < k; i++) {                 if(nums[i] == cur) {                     c = (char) ('a'+i);                 }             }             for(int i = cur + 1; i <= n; i++) {                 sb.append(c);             }             bw.write(sb.toString()+"\n");         }         bw.flush();         br.close();         bw.close();     } }
   |