Codeforces Round #632 (Div. 2)

比赛链接:https://codeforces.com/contest/1333
个人博客:http://www.kindkidll.com/index.php/archives/134/

A. Little Artem

题意:B是有白格子相邻的黑格子数,W是有黑格子相邻的白格子数,合法涂色方案为B=W+1。
刚开始看错题意,以为是给定矩阵和黑白格子数构造合法方案,心想好像有点难,就跑去做B题了。回来发现原来只给定了矩阵,而且 2 ≤ n , m ≤ 100 2 \leq n,m \leq 100 2n,m100,那么只需要使一个角落的格子为白色其余格子全为黑色即可。

#include 
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 2e5 + 117;
const int MAXM = 2e5 + 117;

int t;
int n, m;
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &n, &m);
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(i == 0 && j == 0) putchar('W');
                else putchar('B');
            }
            putchar(10);
        }
    }
    return 0;
}

B. Kind Anton

题意:给定a、b序列, − 1 ≤ a i ≤ 1 , − 1 0 9 ≤ b i ≤ 1 0 9 -1 \leq a_i \leq 1,-10^9 \leq b_i \leq 10^9 1ai1,109bi109,若 j < i jj<i则可以进行操作 a j = a j + a i a_j=a_j+a_i aj=aj+ai,问能不能使a序列变成b序列。

基本思路:从后往前操作,若 b [ i ] < a [ i ] b[i]b[i]<a[i],要使 a [ i ] a[i] a[i]变小则在 a [ i ] a[i] a[i]的前面必须要存在-1,若不存在-1则不合法。同理若 b [ i ] > a [ i ] b[i]>a[i] b[i]>a[i],要使 a [ i ] a[i] a[i]变大则在 a [ i ] a[i] a[i]的前面必须要存在1,否则也不合法。每一次都要往前找是否存在1或者-1,则记住最左边的1和-1的位置每一次就可以 O ( 1 ) O(1) O(1)的判断了。

#include 
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 2e5 + 117;
const int MAXM = 2e5 + 117;

int t;
int n, fu, one;
int a[MAXN], b[MAXN];
int main() {
    scanf("%d", &t);
    while(t--) {
        fu = one = INF;
        scanf("%d", &n);
        for(int i = 0; i < n; i++) {
            scanf("%d", &a[i]);
            if(a[i] == 1 && one == INF) one = i;
            if(a[i] == -1 && fu == INF) fu = i;
        }
        bool pr = true;
        for(int i = 0; i < n; i++) {
            scanf("%d", &b[i]);
            if(b[i] > a[i] && one >= i) pr = false;
            if(b[i] < a[i] && fu >= i) pr = false;
        }
        if(pr) puts("YES");
        else puts("NO");
    }
    return 0;
}

C. Eugene and an array

题意:如果一个序列的所有非空子序列的和都不为零,则称该序列是一个好序列。现给定一个序列,求有多少个非空子序列是好序列。

基本思路:枚举右端点,对于每一个右端点求有多少个左端点满足要求。注意到枚举右端点已经有 O ( n ) O(n) O(n)的复杂度了,那么在求左端点数量的时候需要一定的效率。

限制一:对于任意的 j j j,若区间 [ i , j ] [i,j] [i,j]的和为0,则区间 [ i , j ] [i,j] [i,j]不是一个好序列,区间 [ i − 1 , j ] [i-1,j] [i1,j]也不是一个好序列,因为存在子序列的和为0。也就是说我们只要找到最大的 i i i满足 [ i , j ] [i,j] [i,j]的和为0,则左端点小于 i i i的区间都不是好序列,左端点大于 i i i的区间都是好序列。那么问题是如何快速的寻找到最大的 i i i

前缀和:设 s u m [ i ] sum[i] sum[i]表示序列的前 i i i项和,若 s u m [ i ] = s u m [ j ] sum[i]=sum[j] sum[i]=sum[j],则区间 [ i , j ] [i,j] [i,j]的和等于 s u n [ j ] − s u m [ i ] = 0 sun[j]-sum[i]=0 sun[j]sum[i]=0。那么问题由求某段区间的和为0转化为求前缀和相同的位置,我们可以使用map或者离散化后二分来得到某一个前缀和最后一次在哪个位置出现,单次查询的复杂度都是 O ( l o g 2 n ) O(log_{2}{n}) O(log2n)

限制二:若区间 [ i , j ] [i,j] [i,j]的子区间 [ k , l ] [k,l] [k,l]的和为0,则区间 [ i , j ] [i,j] [i,j]不是一个好区间。也就是说对于任意的 j j j左端点在往左拓展的时候不仅要考虑本身的区间和是否为0,还要考虑是否会包含进和为0的子区间。那么我们找到左边区间和为0的区间中最右边的一个,假设是 [ k , l ] [k,l] [k,l],那么 j j j的左端点最多拓展到 k + 1 k+1 k+1

#include 
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 2e5 + 117;
const int MAXM = 2e5 + 117;

int n;
int last;
LL ans, a[MAXN];
map<LL, int> id;
int main() {
    id[0] = 1;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        a[i] += a[i - 1];
        if(id[a[i]]) last = max(last, id[a[i]]);
        ans += i - last;
        id[a[i]] = i + 1;
    }
    printf("%lld\n", ans);
    return 0;
}

D. Challenges in school №41

题意:把右键头抽象成1,左箭头抽象成0。给定一个长度为 n n n的01序列,一次操作选择至少一个 a [ i ] > a [ i + 1 ] a[i]>a[i+1] a[i]>a[i+1]的二元组 ( i , i + 1 ) (i,i+1) (i,i+1),交换 a [ i ] a[i] a[i] a [ i + 1 ] a[i+1] a[i+1]。问能否恰好使用 k k k次操作得到一个递增序列,并输出每次操作选择交换的二元组左边的下标。

基本思路:类比冒泡排序去模拟一遍交换的过程,时间复杂度 O ( n 2 ) O(n^2) O(n2)。在模拟的过程中记录下每一轮进行交换的位置以及总的轮数 c n t cnt cnt和总的交换次数 s u m sum sum。则必须满足 c n t ≤ k ≤ s u m cnt \leq k \leq sum cntksum才有合法解。

贪心:再来考虑输出的问题。贪心的让前面的操作进行尽可能多的交换,但必须给后面的操作留至少一次交换。

#include 
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 3e3 + 117;
const int MAXM = 2e5 + 117;

int n, k;
char s[MAXN];
int cnt, sum, a[MAXN];
vector<int> ans[MAXN];
void solve() {
    cnt = sum = 0;
    while(true) {
        bool f = true;
        for(int i = 1; i < n; i++) {
            if(a[i] > a[i + 1]) {
                swap(a[i], a[i + 1]);
                ans[cnt].push_back(i);
                f = false;
                sum++;
                i++;
            }
        }
        if(f) break;
        cnt++;
    }
}
void out() {
    int x = 0, y = 0, z = ans[0].size();
    while(k--) {
        int dy = min(z - y, sum - k);
        printf("%d", dy);
        sum -= dy;
        while(dy--) printf(" %d", ans[x][y++]);
        if(y == z) {
            x++;
            y = 0;
            z = ans[x].size();
        }
        putchar(10);
    }
}
int main() {
    scanf("%d%d", &n, &k);
    scanf("%s", s + 1);
    for(int i = 1; i <= n; i++) {
        if(s[i] == 'R') a[i] = 1;
        else a[i] = 0;
    }
    solve();
    if(cnt <= k && k <= sum) out();
    else puts("-1");
    return 0;
}

E. Road to 1600

题意:给出国际象棋中车和皇后的走法,给定一个 n n n阶的棋盘,每个格子有权值且互不相同,并给出下列约束:

  • 初始棋子在权值为1的点,并该点被标记为访问过。
  • 如果合法能到的点中有未访问的点,则走到权值最小的点。
  • 否则如果还有为访问的点,则走到权值最小的点并且费用+1。
  • 否则结束程序。

需要构造一个棋盘使得车的费用严格小于皇后的费用。

基本思路:构造一个棋盘使得车的费用为0而皇后的费用为1。

  • n < 3 n<3 n<3的时候无解

  • n = 3 n=3 n=3的时候通过搜索或者手动构造基本解,例如下面是一个合法的解:

    /*
    5 9 1
    4 3 2
    8 7 6
    */
    
  • n > 3 n>3 n>3的时候,采用递归套娃的思想如果能使 n n n阶转化成3阶岂不美滋滋,如下所示只需要区分一下奇数和偶数的情况,剩下的3阶只需要用构造出来的基本解进行转化即可。

    /*
    * * * 7
    * * * 6
    * * * 5
    1 2 3 4
    */
    /*
    * * * 1 16
    * * * 2 15
    * * * 3 14
    7 6 5 4 13
    8 9 10 11 12
    */
    
#include 
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 5e5 + 117;
const int MAXM = 2e5 + 117;

int n;
int s[7][7] = {{5, 9, 1}, {4, 3, 2}, {8, 7, 6}};
int t[517][517];
void left_up(int id, int num);
void down_right(int id, int num) {
    for(int i = 0; i <= id; i++)//向下
        t[i][id] = num++;
    for(int j = id - 1; j >= 0; j--)//向左
        t[id][j] = num++;
    if(id + 1 < n) left_up(id + 1, num);
}
void left_up(int id, int num) {
    for(int j = 0; j <= id; j++)//向右
        t[id][j] = num++;
    for(int i = id - 1; i >= 0; i--)//向上
        t[i][id] = num++;
    if(id + 1 < n) down_right(id + 1, num);
}
int main() {
    scanf("%d", &n);
    if(n < 3) puts("-1");
    else {
        if(n % 2 == 0) left_up(3, 1);
        else  down_right(3, 1);
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                if(i < 3 && j < 3) printf("%d ", n * n - 9 + s[i][j]);
                else printf("%d ", t[i][j]);
            }
            putchar(10);
        }
    }
    return 0;
}

F. Kate and imperfection

题意:定义 I i I_i Ii是大小为 i i i的集合中 g c d ( a , b ) gcd(a,b) gcd(a,b)的最大值。给定一个全排列的集合,求不同大小的子集的最小 I i I_i Ii

基本思路:贪心的是构造尽量小的 g c d ( a , b ) gcd(a,b) gcd(a,b)

  • 首先构造 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1的情况,这时候只能把素数加进集合。
  • 再构造 g c d ( a , b ) = 2 gcd(a,b)=2 gcd(a,b)=2的情况,可以把4加进集合。
  • 再构造 g c d ( a , b ) = 3 gcd(a,b)=3 gcd(a,b)=3的情况,可以把6和9加进集合。
  • 观察一下前面的规律,为什么在 g c d ( a , b ) = 2 gcd(a,b)=2 gcd(a,b)=2的时候不能加8以及在 g c d ( a , b ) = 3 gcd(a,b)=3 gcd(a,b)=3的时候不能加12?
  • 一个数总可以分解成最小质因子乘以某个数,我们不仅要关心最小质因子是多少,还要关心那个乘数。例如 8 = 2 ∗ 4 8=2*4 8=24,虽然8的最小质因子是2,但是乘数是4, g c d ( 4 , 8 ) = 4 gcd(4,8)=4 gcd(4,8)=4使得 I i I_i Ii由2增大到4。
  • 也就是说一个数x对答案的影响是x/最小质因子,用欧拉筛在 O ( n ) O(n) O(n)的时间复杂度下就能得到每个数对答案的影响,再排序一遍就是最优方案。
#include 
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 5e5 + 117;
const int MAXM = 2e5 + 117;

int n;
int prime[MAXN], ans[MAXN];
int main() {
    scanf("%d", &n);
    for(int i = 2; i <= n; i++) {
        if(!prime[i]) {
            prime[++prime[0]] = i;
            ans[i] = 1;
        }
        for(int j = 1; j <= prime[0] && prime[j] <= n / i; j++) {
            prime[prime[j]*i] = 1;
            ans[prime[j]*i] = i;
            if(i % prime[j] == 0) break;
        }
    }
    sort(ans + 2, ans + n + 1);
    for(int i = 2; i < n; i++) printf("%d ", ans[i]);
    printf("%d\n", ans[n]);
    return 0;
}

你可能感兴趣的:(题解)