比赛链接:https://codeforces.com/contest/1333
个人博客:http://www.kindkidll.com/index.php/archives/134/
题意:B是有白格子相邻的黑格子数,W是有黑格子相邻的白格子数,合法涂色方案为B=W+1。
刚开始看错题意,以为是给定矩阵和黑白格子数构造合法方案,心想好像有点难,就跑去做B题了。回来发现原来只给定了矩阵,而且 2 ≤ n , m ≤ 100 2 \leq n,m \leq 100 2≤n,m≤100,那么只需要使一个角落的格子为白色其余格子全为黑色即可。
#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;
}
题意:给定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 −1≤ai≤1,−109≤bi≤109,若 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;
}
题意:如果一个序列的所有非空子序列的和都不为零,则称该序列是一个好序列。现给定一个序列,求有多少个非空子序列是好序列。
基本思路:枚举右端点,对于每一个右端点求有多少个左端点满足要求。注意到枚举右端点已经有 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] [i−1,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;
}
题意:把右键头抽象成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 cnt≤k≤sum才有合法解。
贪心:再来考虑输出的问题。贪心的让前面的操作进行尽可能多的交换,但必须给后面的操作留至少一次交换。
#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;
}
题意:给出国际象棋中车和皇后的走法,给定一个 n n n阶的棋盘,每个格子有权值且互不相同,并给出下列约束:
需要构造一个棋盘使得车的费用严格小于皇后的费用。
基本思路:构造一个棋盘使得车的费用为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;
}
题意:定义 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)。
#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;
}