Codeforces Round #663 (Div. 2)A~D总结

比赛传送门

Codeforces Round #663 Div. 2

A题

☠速度要upup!

**题意** 给出n,求长度为n的一个数组 这个数组满足: 对于p的每个子数组,其中所有元素的**异或**均不小于该子数组中元素的数量

题解
显示长度为len的任何子数组至少具有一个大于或等于len的元素即可

只要一大一小穿插在数组里,就好了
eg. 6 ==> 1 6 2 5 4 3

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e5 + 10;
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n;
        scanf("%d", &n);
        int cnt = 1;
        for(int i = 1;; i++) {
            if(cnt > n) break;
                printf("%d ", i);
            cnt++;
            if(cnt > n) break;
                printf("%d ",  n - i + 1);
            cnt++;
        }
    }
}

B题

☠思维卡住啦,怎么能没有想到呢!

题意
使用由n行和m列组成的网格表示的传送带,R代表向右,D代表向下。
如果行李在任何时候从网格中移出,则视为丢失。

确定更改的最小单元数,使无论其最初放置在哪个单元中,都可以最终进入该单元格(n,m)。

题解
放在(n - 1) * (m - 1) 的无论怎么移动(只能向下或者向右),都会移动到最后一列和最后一行

所以只要关注最后一列为’R’,最后一行为’D’


#include <bits/stdc++.h>
using namespace std;
char field[102][102];
 
int main() {
    int T;
    scanf("%d", &T);;
    while(T--) {
        int n, m;
        scanf("%d%d", &n, &m);
        memset(field, 0, sizeof(field));
        for(int i = 0; i < n; i++) scanf("%s", field[i]);
        int ans = 0;
        for(int i = 0; i < n - 1; i++)
            if(field[i][m - 1] == 'R') ans++;
        for(int i = 0; i < m - 1; i++)
            if(field[n - 1][i] == 'D') ans++;
        printf("%d\n", ans);
    }
}

C题

☠题意最关键,新的知识点牢记!

题意
对于每一个i∈{1,2,3,…,n},分别在它左右两侧找到距离最近且比pi大的元素pj在i和j中间连一
条无向边。如果找不到就不连。(注意是按下标连边,不是按值连边)

题解
对于一个元素i,如果它的两侧均有大于i的元素,那么必然会成环.
比如3 1 2,因为有两个峰,且这两个峰不相等,所以会成环。

那么显然只有在单峰的时候不满足。

答案为 全排列个数 = 单峰排列个数
即:n!-2^(n-1)

单峰排列

首先明确一点:n必须是“山峰”的最高点,然后依次是n-1,n-2,…,1。其中,n-1这个数可以放在左边的“山”,也可以放在右边的“山”,然后n-2亦是如此……直到1,都有两种放置方案,故最后的结果就是2(n-1)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int MOD = 1e9 + 7;
 
ll qpow(ll x, ll n) {
    ll ans = 1LL;
    while(n) {
        if(n & 1) ans = (ans * x) % MOD;
        x = (x * x) % MOD;
        n >>= 1;
    }
    return ans;
}
 
int main() {
    int n;
    scanf("%d", &n);
    ll ans = 1;
    for(int i = 1; i <= n; i++) {
        ans *= i;
        ans %= MOD;
    }
    ans -= qpow(2, n - 1);
    ans %= MOD;
    if(ans < 0) ans += MOD;
    printf("%lld\n", ans);
}

D题

☠体会思维吧! + dp学习

题意

如果每个偶数长的平方子矩阵都具有奇数个1,则该二进制矩阵称为good。
修改矩阵满足good,如果不可以作为输出-1,否者输出改变的次数

题解

//问题转变(思维):
首先,如果min(n,m)> 3,则不存在解,因为这意味着网格包含至少一个4×4子矩阵,该子矩阵可以进一步分解为四个2×2子矩阵。
由于所有这两个2×2子矩阵都假定具有奇数个,因此它们的并集将具有偶数个。

现在,问题减少到改变单元的最少数量,以使每个2×2子矩阵具有奇数个子矩阵-这对于每个有效网格都可以实现。

所以,n,m大于1,小于等于4。(n==1时需要改变0个)
即矩阵形式:2*k, 3*k, k*\2, k*3(k >= 2)
又因为n <= m,那么只有 2*k1(k1 >= 2), 3*k2(k2 >= 3)

也就是说对应的我们只有两行的2 × 2矩阵,我们把这些矩阵包含的1的个数统计出来,按照是否需要改变分类为0和1两种情况,需要改变的矩阵是1,不需要改变的矩阵是0。

//dp
把每一列的状态存在num[]数组里,我们的目的是算出把整个num数组变为0的最小操作次数
假如num[i] = 2 = 10(二进制)说明第二列的2*2矩阵1的个数为偶数,需要改变

转移方程dp:
dp[i][j]代表到第i列 矩阵状态为j 时要进行的操作数
矩阵状态j: eg, j = 3 = 11(二进制),说明第一、二行开始的2*2矩阵都需要改变状态

思路参考

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 1e6 + 10;
const int INF = 1e9 + 7;

string a[MAX];
int field[3][MAX];
int dp[MAX][4];
int num[MAX];

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i++) cin >> a[i];
    if(n == 1 || m == 1) cout << 0 << endl;
    else if(n >= 4 && m >= 4) cout << -1 << endl;
    else {
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++)
                field[i][j] = a[i][j] - '0';
        }
        for(int i = 0; i < n - 1; i++) {
            for(int j = 0; j < m - 1; j++) {
                int cnt = 0;
                if(field[i][j] == 1) cnt++;
                if(field[i + 1][j] == 1) cnt++;
                if(field[i][j + 1] == 1) cnt++;
                if(field[i + 1][j + 1] == 1) cnt++;
                if(cnt % 2 == 0) num[j + 1] += (1 << i); //从第一列开始
            }
        }
        int len = 1 << n;
        //从第一列开始,第0列都为0
        dp[0][0] = dp[0][1] = dp[0][2] = dp[0][3] = 0;
        //第1 ~ m-1 列
        for(int i = 1; i < m; i++) {
            for(int j = 0; j < len; j++) { //j 是改变的方式
                // eg, j = 0,不改变; j = 3, 改变一、二列
                if(!j) dp[i][0 ^ num[i]] = dp[i - 1][0];
                else dp[i][j ^ num[i]] = dp[i - 1][j] + 1;
            }
        }
        int ans = INF;
        //dp[m - 1][i] + 1,因为改变后面几列,第一列也会变+1
        for(int i = 1; i < len; i++) {
            ans = min(ans ,dp[m - 1][i] + 1);
        }
        ans = min(ans, dp[m - 1][0]);
        cout << ans << endl;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值