Codeforces Round #663 Div2 A~D 题解
作者:互联网
本场链接:[Codeforces Round #663 Div2](https://codeforces.ml/contest/1391)
# 闲话
手速场,但是D算一个分水岭.本场的话开到D就有200名.
# A. Suborrays 原题大意:构造一个排列$p$满足任意一组连续子序列的或和不小于整段区间的长度,即$p_i | p_{i+1} | ... | p_j \geq j - i + 1$
## 思路 显然$1,2,3,4,....,n$满足题意.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll;
int main() { int T;scanf("%d",&T); while(T--) { int n;scanf("%d",&n); for(int i = 1;i <= n;++i) printf("%d ",i); puts(""); } return 0; } ```
# B. Fix You 原题大意:给定一个$n*m$的棋盘,棋盘上有很多字母,代表在这个格子上只能往某个方向上移动.现要求棋盘上所有的点都能到达右下角的终点,问最少修改几个可以达成.输出次数. 数据范围: $1 \leq n,m \leq 100$
## 思路 从棋盘上每一个点往外BFS拓展,并且记录经过了哪些点,如果最终到达了终点,就把所有经过的点特殊标记,如果以后的过程走到了这样的一个点,说明往后一定可以走到终点.除此之外,设立一个普通记录,即表示最终没有走到终点,但是这个点被拓展过了,不能额外的拓展.最后返回是否能到达终点,统计不能走到终点的个数,即为要修改的次数.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pii; #define x first #define y second const int N = 105; char g[N][N]; int n,m,st[N][N]; int bfs(int sta,int end) { queue<pii> q;q.push({sta,end}); vector<pii> op;op.push_back({sta,end}); int ok = 0; while(!q.empty()) { auto t = q.front();q.pop(); int x = t.first,y = t.second; if(st[x][y] == 2 || (x == n && y == m)) { ok = 1; break; } st[x][y] = 1;
if(g[x][y] == 'R') { int a = x,b = y + 1; if(b > m) continue; q.push({a,b}); op.push_back({a,b}); } if(g[x][y] == 'D') { int a = x + 1,b = y; if(a > n) continue; q.push({a,b}); op.push_back({a,b}); } } for(auto& t : op) st[t.first][t.second] = 2; return !ok; } int main() { int T;scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); memset(st,0,sizeof st); for(int i = 1;i <= n;++i) scanf("%s",g[i] + 1),getchar(); int fres = 0; for(int i = 1;i <= n;++i) { for(int j = 1;j <= m;++j) { if(!st[i][j]) { int r = bfs(i,j); // if(r) cout << i << " " << j << endl; fres += r; } } } printf("%d\n",fres); } return 0; } ```
# C. Cyclic Permutations 原题大意:对于一个长度为$n$的排列,里面的每一个元素,向他左边最近且比他大的元素连边,以及右边最近且比他大的元素连边.问所有长度为$n$的排列里,至少包含一个简单环的排列有多少个.注意边是无向边.答案对$10^9+7$取模. 数据范围: $3 \leq n \leq 10^6$
## 思路 模拟一下样例可以发现,如果有环出现的话,是有一个波谷才会出现的.而且这个波谷还得是连续的,就三位元素里出现的波谷.进一步可以发现至少存在一个环的条件很傻逼,换成全排列$n!$再抠掉不存在环的排列数就是答案.后者即整个排列里不存在一个波谷,也即一个单峰排列.并且是左上右下的,而且波峰必然是$n$.因此构造排列的方式就等价于说在$n$旁边插入元素,每个元素有两种选择,一共就是$2^{n-1}$.因此最后的答案就是$n!-2^{n-1}$.由于不需要定点的查,所以一开始直接递推求阶乘,再套一个快速幂模板就轻松过掉本题了. 注意有减法操作,防范负数取模.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6+7,MOD = 1e9 + 7; ll fact[N]; void init() { fact[0] = 1; for(int i = 1;i < N;++i) fact[i] = (fact[i - 1] * i) % MOD; } ll qmul(ll a, ll b, ll P) { ll L = a * (b >> 25LL) % P * (1LL << 25) % P; ll R = a * (b & ((1LL << 25) - 1)) % P; return (L + R) % P; } ll qpow(ll a,ll b,ll p) { ll res = 1 % p; while(b) { if(b & 1) res = qmul(res,a,p); a = qmul(a,a,p); b >>= 1; } return res; } int main() { init(); int n;scanf("%d",&n); ll res = ((fact[n] - qpow(2,n - 1,MOD)) % MOD + MOD) % MOD; printf("%lld",res); return 0; } ```
# D. 505 原题大意;给定一个$n*m$的二进制矩阵.定义一个二进制矩阵是牛逼的,当且仅当所有的边长为偶数的正方形区域里和1的个数是奇数.现给定一个矩阵,问最少要修改其中的几个元素才能使它变成牛逼的二进制矩阵. 数据范围: $1 \leq n \leq m \leq 10^6$ $1 \leq n * m \leq 10^6$
## 思路 这题看起来很牛逼,数据范围比较大.从数据范围来想的话,做法肯定不能暴力,而且就这个范围都不知道怎么开的下空间存.进而可以猜测一下是不是有范围问题.模拟一下数据之后可以发现当$n,m$都大于等于4的时候,整个矩阵必然无解.因为这样就存在一个4*4的正方形,而整个里面必然出现偶数个1,导致不符合条件.又由于是要满足所有的存在的可行方案都不能有,所以必然整个问题都无解.就可以将范围压下来了:两个边长至少有一个比4小.而且题目规定了$n\leq m$.所以$n \leq 3$. 由于情况很少,可以直接讨论: ①$n=1$显然不需要修改. ②$n=2$,可以发现棋盘就是一个长条,由于范围的原因,只可能有2*2的正方形存在,从左到右DP就能解决这个问题. ③$n=3$,同②,只不过现在是三行. 这个时候思路基本就可以确定了,就是一个从左往右推的DP,由于情况相当的少,可以暴力枚举做掉这道题.下面讲一下DP过程
### 状态设计 状态:$f[i][j][k]$表示当前在$i$列,上面一个是$j$下面一个是$k$.并且将整个矩阵到$i$列为止都修改到合法的最小代价. 入口:枚举第一列的选择和不等的部分. 转移:枚举本列的四种情况.知道本列是什么状态,自然可以找出上一列合法的状态有哪些,直接拿过来用就可以了.并且还要加上本列有哪些不同. 出口:最后一列的答案最小者.
到这里,基本就可以写完第一个情况了.而第③个情况只是②多加了一行,本质是相同的.这个题唯一的难点就在于枚举很麻烦,本身并不算难.剩下的建议自己手推.代码实现的时候用引用可以减少一点码量,也可以看得更清晰.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6 + 7,M = 5; ll f[N][2][2],dp[N][2][2][2]; char g[M][N]; int n,m; inline int cost(int x,int y,int c) { int res = 0; if(g[1][c] - '0' != x) ++res; if(g[2][c] - '0' != y) ++res; return res; } inline int cost(int x,int y,int z,int c) { int res = 0; if(g[1][c] - '0' != x) ++res; if(g[2][c] - '0' != y) ++res; if(g[3][c] - '0' != z) ++res; return res; } ll slove1() { for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) f[1][i][j] = cost(i,j,1); for(int i = 2;i <= m;++i) { auto& p = f[i],&q = f[i - 1]; p[0][0] = min(q[0][1],q[1][0]) + cost(0,0,i); p[0][1] = min(q[0][0],q[1][1]) + cost(0,1,i); p[1][0] = min(q[0][0],q[1][1]) + cost(1,0,i); p[1][1] = min(q[0][1],q[1][0]) + cost(1,1,i); } ll res = 1e18; for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) res = min(res,f[m][i][j]); return res; } ll slove2() { for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) for(int k = 0;k <= 1;++k) dp[1][i][j][k] = cost(i,j,k,1); for(int i = 2;i <= m;++i) { auto& p = dp[i],&q = dp[i - 1]; p[0][0][0] = min(q[1][0][1],q[0][1][0]) + cost(0,0,0,i); p[0][0][1] = min(q[1][0][0],q[0][1][1]) + cost(0,0,1,i); p[0][1][0] = min(q[0][0][0],q[1][1][1]) + cost(0,1,0,i); p[1][0][0] = min(q[0][0][1],q[1][1][0]) + cost(1,0,0,i); p[1][0][1] = min(q[0][0][0],q[1][1][1]) + cost(1,0,1,i); p[1][1][0] = min(q[1][0][0],q[0][1][1]) + cost(1,1,0,i); p[0][1][1] = min(q[0][0][1],q[1][1][0]) + cost(0,1,1,i); p[1][1][1] = min(q[0][1][0],q[1][0][1]) + cost(1,1,1,i); } ll res = 1e18; for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) for(int k = 0;k <= 1;++k) res = min(dp[m][i][j][k],res); return res; } int main() { scanf("%d%d",&n,&m); if(n >= 4 && m >= 4) return puts("-1"),0; for(int i = 1;i <= n;++i) scanf("%s",g[i] + 1); if(n == 1) return puts("0"),0; if(n == 2) printf("%lld",slove1()); if(n == 3) printf("%lld",slove2()); return 0; } ```
# A. Suborrays 原题大意:构造一个排列$p$满足任意一组连续子序列的或和不小于整段区间的长度,即$p_i | p_{i+1} | ... | p_j \geq j - i + 1$
## 思路 显然$1,2,3,4,....,n$满足题意.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll;
int main() { int T;scanf("%d",&T); while(T--) { int n;scanf("%d",&n); for(int i = 1;i <= n;++i) printf("%d ",i); puts(""); } return 0; } ```
# B. Fix You 原题大意:给定一个$n*m$的棋盘,棋盘上有很多字母,代表在这个格子上只能往某个方向上移动.现要求棋盘上所有的点都能到达右下角的终点,问最少修改几个可以达成.输出次数. 数据范围: $1 \leq n,m \leq 100$
## 思路 从棋盘上每一个点往外BFS拓展,并且记录经过了哪些点,如果最终到达了终点,就把所有经过的点特殊标记,如果以后的过程走到了这样的一个点,说明往后一定可以走到终点.除此之外,设立一个普通记录,即表示最终没有走到终点,但是这个点被拓展过了,不能额外的拓展.最后返回是否能到达终点,统计不能走到终点的个数,即为要修改的次数.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pii; #define x first #define y second const int N = 105; char g[N][N]; int n,m,st[N][N]; int bfs(int sta,int end) { queue<pii> q;q.push({sta,end}); vector<pii> op;op.push_back({sta,end}); int ok = 0; while(!q.empty()) { auto t = q.front();q.pop(); int x = t.first,y = t.second; if(st[x][y] == 2 || (x == n && y == m)) { ok = 1; break; } st[x][y] = 1;
if(g[x][y] == 'R') { int a = x,b = y + 1; if(b > m) continue; q.push({a,b}); op.push_back({a,b}); } if(g[x][y] == 'D') { int a = x + 1,b = y; if(a > n) continue; q.push({a,b}); op.push_back({a,b}); } } for(auto& t : op) st[t.first][t.second] = 2; return !ok; } int main() { int T;scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); memset(st,0,sizeof st); for(int i = 1;i <= n;++i) scanf("%s",g[i] + 1),getchar(); int fres = 0; for(int i = 1;i <= n;++i) { for(int j = 1;j <= m;++j) { if(!st[i][j]) { int r = bfs(i,j); // if(r) cout << i << " " << j << endl; fres += r; } } } printf("%d\n",fres); } return 0; } ```
# C. Cyclic Permutations 原题大意:对于一个长度为$n$的排列,里面的每一个元素,向他左边最近且比他大的元素连边,以及右边最近且比他大的元素连边.问所有长度为$n$的排列里,至少包含一个简单环的排列有多少个.注意边是无向边.答案对$10^9+7$取模. 数据范围: $3 \leq n \leq 10^6$
## 思路 模拟一下样例可以发现,如果有环出现的话,是有一个波谷才会出现的.而且这个波谷还得是连续的,就三位元素里出现的波谷.进一步可以发现至少存在一个环的条件很傻逼,换成全排列$n!$再抠掉不存在环的排列数就是答案.后者即整个排列里不存在一个波谷,也即一个单峰排列.并且是左上右下的,而且波峰必然是$n$.因此构造排列的方式就等价于说在$n$旁边插入元素,每个元素有两种选择,一共就是$2^{n-1}$.因此最后的答案就是$n!-2^{n-1}$.由于不需要定点的查,所以一开始直接递推求阶乘,再套一个快速幂模板就轻松过掉本题了. 注意有减法操作,防范负数取模.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6+7,MOD = 1e9 + 7; ll fact[N]; void init() { fact[0] = 1; for(int i = 1;i < N;++i) fact[i] = (fact[i - 1] * i) % MOD; } ll qmul(ll a, ll b, ll P) { ll L = a * (b >> 25LL) % P * (1LL << 25) % P; ll R = a * (b & ((1LL << 25) - 1)) % P; return (L + R) % P; } ll qpow(ll a,ll b,ll p) { ll res = 1 % p; while(b) { if(b & 1) res = qmul(res,a,p); a = qmul(a,a,p); b >>= 1; } return res; } int main() { init(); int n;scanf("%d",&n); ll res = ((fact[n] - qpow(2,n - 1,MOD)) % MOD + MOD) % MOD; printf("%lld",res); return 0; } ```
# D. 505 原题大意;给定一个$n*m$的二进制矩阵.定义一个二进制矩阵是牛逼的,当且仅当所有的边长为偶数的正方形区域里和1的个数是奇数.现给定一个矩阵,问最少要修改其中的几个元素才能使它变成牛逼的二进制矩阵. 数据范围: $1 \leq n \leq m \leq 10^6$ $1 \leq n * m \leq 10^6$
## 思路 这题看起来很牛逼,数据范围比较大.从数据范围来想的话,做法肯定不能暴力,而且就这个范围都不知道怎么开的下空间存.进而可以猜测一下是不是有范围问题.模拟一下数据之后可以发现当$n,m$都大于等于4的时候,整个矩阵必然无解.因为这样就存在一个4*4的正方形,而整个里面必然出现偶数个1,导致不符合条件.又由于是要满足所有的存在的可行方案都不能有,所以必然整个问题都无解.就可以将范围压下来了:两个边长至少有一个比4小.而且题目规定了$n\leq m$.所以$n \leq 3$. 由于情况很少,可以直接讨论: ①$n=1$显然不需要修改. ②$n=2$,可以发现棋盘就是一个长条,由于范围的原因,只可能有2*2的正方形存在,从左到右DP就能解决这个问题. ③$n=3$,同②,只不过现在是三行. 这个时候思路基本就可以确定了,就是一个从左往右推的DP,由于情况相当的少,可以暴力枚举做掉这道题.下面讲一下DP过程
### 状态设计 状态:$f[i][j][k]$表示当前在$i$列,上面一个是$j$下面一个是$k$.并且将整个矩阵到$i$列为止都修改到合法的最小代价. 入口:枚举第一列的选择和不等的部分. 转移:枚举本列的四种情况.知道本列是什么状态,自然可以找出上一列合法的状态有哪些,直接拿过来用就可以了.并且还要加上本列有哪些不同. 出口:最后一列的答案最小者.
到这里,基本就可以写完第一个情况了.而第③个情况只是②多加了一行,本质是相同的.这个题唯一的难点就在于枚举很麻烦,本身并不算难.剩下的建议自己手推.代码实现的时候用引用可以减少一点码量,也可以看得更清晰.
## 代码 ```C++ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6 + 7,M = 5; ll f[N][2][2],dp[N][2][2][2]; char g[M][N]; int n,m; inline int cost(int x,int y,int c) { int res = 0; if(g[1][c] - '0' != x) ++res; if(g[2][c] - '0' != y) ++res; return res; } inline int cost(int x,int y,int z,int c) { int res = 0; if(g[1][c] - '0' != x) ++res; if(g[2][c] - '0' != y) ++res; if(g[3][c] - '0' != z) ++res; return res; } ll slove1() { for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) f[1][i][j] = cost(i,j,1); for(int i = 2;i <= m;++i) { auto& p = f[i],&q = f[i - 1]; p[0][0] = min(q[0][1],q[1][0]) + cost(0,0,i); p[0][1] = min(q[0][0],q[1][1]) + cost(0,1,i); p[1][0] = min(q[0][0],q[1][1]) + cost(1,0,i); p[1][1] = min(q[0][1],q[1][0]) + cost(1,1,i); } ll res = 1e18; for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) res = min(res,f[m][i][j]); return res; } ll slove2() { for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) for(int k = 0;k <= 1;++k) dp[1][i][j][k] = cost(i,j,k,1); for(int i = 2;i <= m;++i) { auto& p = dp[i],&q = dp[i - 1]; p[0][0][0] = min(q[1][0][1],q[0][1][0]) + cost(0,0,0,i); p[0][0][1] = min(q[1][0][0],q[0][1][1]) + cost(0,0,1,i); p[0][1][0] = min(q[0][0][0],q[1][1][1]) + cost(0,1,0,i); p[1][0][0] = min(q[0][0][1],q[1][1][0]) + cost(1,0,0,i); p[1][0][1] = min(q[0][0][0],q[1][1][1]) + cost(1,0,1,i); p[1][1][0] = min(q[1][0][0],q[0][1][1]) + cost(1,1,0,i); p[0][1][1] = min(q[0][0][1],q[1][1][0]) + cost(0,1,1,i); p[1][1][1] = min(q[0][1][0],q[1][0][1]) + cost(1,1,1,i); } ll res = 1e18; for(int i = 0;i <= 1;++i) for(int j = 0;j <= 1;++j) for(int k = 0;k <= 1;++k) res = min(dp[m][i][j][k],res); return res; } int main() { scanf("%d%d",&n,&m); if(n >= 4 && m >= 4) return puts("-1"),0; for(int i = 1;i <= n;++i) scanf("%s",g[i] + 1); if(n == 1) return puts("0"),0; if(n == 2) printf("%lld",slove1()); if(n == 3) printf("%lld",slove2()); return 0; } ```
标签:int,题解,ll,663,Codeforces,long,leq,res,## 来源: https://www.cnblogs.com/HotPants/p/13475394.html