C. Cyclic Permutations
题意
给定一个大小为 n n n的排列 { a i } \{a_i\} {ai},按照以下方式建图:
- 对于每个满足 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n的 i i i,找到最大的 j j j,使得其满足 1 ≤ j < i 1 \leq j < i 1≤j<i和 a j > a i a_j>a_i aj>ai,连一条无向边 ( i , j ) (i,j) (i,j)。
- 对于每个满足
1
≤
i
≤
n
1 \leq i \leq n
1≤i≤n的
i
i
i,找到最小的
j
j
j,使得其满足
i
<
j
≤
n
i < j \leq n
i<j≤n和
a
j
>
a
i
a_j>a_i
aj>ai,连一条无向边
(
i
,
j
)
(i,j)
(i,j)。
若一个排列对应的图存在环,则该排列被定义为“循环排列”。
已知 n n n,求“循环排列”数。
思路
Note疯狂暗示
Nodes v 1 , v 2 , … , v k v_1, v_2, …, v_k v1,v2,…,vk form a simple cycle if the following conditions hold:
k ≥ 3 k≥3 k≥3.
v i ≠ v j vi≠vj vi=vj for any pair of indices i i i and j j j. ( 1 ≤ i < j ≤ k ) (1≤i<j≤k) (1≤i<j≤k)
v i v_i vi and v i + 1 v_{i+1} vi+1 share an edge for all i ( 1 ≤ i < k ) i (1≤i<k) i(1≤i<k), and v 1 v_1 v1 and v k v_k vk share an edge.
若一个排列是“循环排列”,该排列对应的图满足存在某一段连续子序列
a
s
,
a
s
+
1
,
⋯
,
s
t
−
1
,
a
t
a_s,a_{s+1},\cdots,s_{t-1},a_t
as,as+1,⋯,st−1,at,
(
t
−
s
+
1
≥
3
)
(t-s+1\geq3)
(t−s+1≥3),排列中相邻两个数连一条边,
s
s
s和
t
t
t连一条边。
上述条件等价于存在某一段连续子序列
a
s
,
a
s
+
1
,
⋯
,
s
t
−
1
,
a
t
a_s,a_{s+1},\cdots,s_{t-1},a_t
as,as+1,⋯,st−1,at,
(
t
−
s
+
1
≥
3
)
(t-s+1\geq3)
(t−s+1≥3),对于每个满足
s
<
i
<
t
s<i<t
s<i<t的
i
i
i都有
a
i
<
m
i
n
{
a
s
,
s
t
}
a_i<min\{a_s,s_t\}
ai<min{as,st},通俗来讲就是存在一个“凹下去”的位置。
考虑从反面入手,用总数减去不合法排列数,结合上面的“循环排列”的判定条件可以发现:不合法排列是“单峰”的(不存在“凹下去”的位置),即最大值到两边依次递减,这样的排列数为
2
n
−
1
2^{n-1}
2n−1,推导也不难。
对于一个大小为
n
n
n的排列,我们按照数从大到小的顺序考虑,对于满足
1
≤
i
<
n
1\leq i <n
1≤i<n的
i
i
i,可以选择放在
i
+
1
i+1
i+1的左边或右边,因此总排列数为
2
n
−
1
2^{n-1}
2n−1。
所以,最终的答案是
n
!
−
2
n
−
1
n!-2^{n-1}
n!−2n−1。
代码
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <iostream>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
typedef long long ll;
const ll mod = 1e9 + 7;
int n;
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
int main()
{
n = getint();
ll s1 = 1, s2 = 1;
fo(i, 2, n)
(s1 *= i) %= mod, (s2 <<= 1) %= mod;
cout << (s1 - s2 + mod) % mod << endl;
return 0;
}
D. 505
题意
给定一个 n n n行 m m m列的 01 01 01矩阵,如果一个 01 01 01矩阵满足每个边长为偶数的正方形子矩阵的和为奇数,该矩阵被定义为“好矩阵”,特别地,若一个矩阵不存在偶数边长的子矩阵,该矩阵也是“好矩阵”。问一个矩阵变成“好矩阵”至少要修改多少个数,若无解输出 − 1 -1 −1。
思路
解法一
若一个矩阵满足
m
i
n
{
n
,
m
}
≥
4
min\{n,m\} \geq 4
min{n,m}≥4则无解,证明:
若存在解,则该矩阵存在一个
4
∗
4
4*4
4∗4的矩阵和为奇数,根据定义,该
4
∗
4
4*4
4∗4的所有
2
∗
2
2*2
2∗2的子矩阵和为奇数,而
4
∗
4
4*4
4∗4的矩阵可以拆成
4
4
4个
2
∗
2
2*2
2∗2子矩阵和之和,该
4
∗
4
4*4
4∗4的矩阵和为偶数,与开始时矩阵和为奇数矛盾。
现在假定
n
≤
m
n\leq m
n≤m,(若
n
>
m
n>m
n>m可以翻转该矩阵),对
n
n
n进行分类讨论:
- n = 1 n=1 n=1,答案为 0 0 0。
- n = 2 n=2 n=2,我们将每一列的两个数求和,得出一个长度为 m m m的序列,最终“好矩阵”对应的序列肯定是奇偶交替的(即:奇偶奇偶 ⋯ \cdots ⋯,或偶奇偶奇 ⋯ \cdots ⋯),两种情况求最小值即可。
- n = 3 n=3 n=3,我们分别将每一列的前两个数求和,后两个数求和,得出一个 2 ∗ m 2*m 2∗m的序列,最终“好矩阵”肯定是以下四种情况之一:
1 | 2 | 3 | ⋯ \cdots ⋯ |
---|---|---|---|
奇 | 偶 | 奇 | ⋯ \cdots ⋯ |
奇 | 偶 | 奇 | ⋯ \cdots ⋯ |
1 | 2 | 3 | ⋯ \cdots ⋯ |
---|---|---|---|
奇 | 偶 | 奇 | ⋯ \cdots ⋯ |
偶 | 奇 | 偶 | ⋯ \cdots ⋯ |
1 | 2 | 3 | ⋯ \cdots ⋯ |
---|---|---|---|
偶 | 奇 | 偶 | ⋯ \cdots ⋯ |
奇 | 偶 | 奇 | ⋯ \cdots ⋯ |
1 | 2 | 3 | ⋯ \cdots ⋯ |
---|---|---|---|
偶 | 奇 | 偶 | ⋯ \cdots ⋯ |
偶 | 奇 | 偶 | ⋯ \cdots ⋯ |
四种情况求最小值即可。
解法二
口胡一下
状压dp,设
f
[
i
]
[
s
]
f[i][s]
f[i][s]表示前
i
i
i列
(
i
>
1
)
(i>1)
(i>1),第
i
−
1
i-1
i−1列的数的
01
01
01状态为
s
s
s,对于第
i
i
i列,枚举每个数填
0
0
0还是
1
1
1,即第
i
i
i列的状态
s
′
s'
s′,转移的时候判断状态
s
s
s和状态
s
′
s'
s′能否拼在一起(即两列的所有
2
∗
2
2*2
2∗2子矩阵和是否为奇数)。
代码
解法一
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <iostream>
using namespace std;
#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)
const int maxm = 1e6 + 5, inf = 1e9;
int n, m;
int s1[maxm], s2[maxm];
char tmp[maxm];
char str[5][maxm];
int getint()
{
char ch;
int res = 0, p;
while (!isdigit(ch = getchar()) && (ch ^ '-'));
p = ch == '-'? ch = getchar(), -1 : 1;
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * p;
}
void work2()
{
fo(i, 1, m) s1[i] = (str[1][i] - '0') ^ (str[2][i] - '0');
int ans = inf;
fo(i, 0, 1)
{
int cnt = 0;
for (int j = 1, now = i; j <= m; j++, now ^= 1) cnt += now ^ s1[j];
ans = min(ans, cnt);
}
printf("%d\n", ans);
}
void work3()
{
fo(i, 1, m) s1[i] = (str[1][i] - '0') ^ (str[2][i] - '0');
fo(i, 1, m) s2[i] = (str[2][i] - '0') ^ (str[3][i] - '0');
int ans = inf;
fo(i, 0, 1)
fo(j, 0, 1)
{
int cnt = 0;
for (int n1 = i, n2 = j, k = 1; k <= m; k++, n1 ^= 1, n2 ^= 1)
cnt += n1 != s1[k] || n2 != s2[k];
ans = min(ans, cnt);
}
printf("%d\n", ans);
}
int main()
{
n = getint(); m = getint();
if (n >= 4 && m >= 4)
{
printf("-1\n");
return 0;
}
if (n < m)
fo(i, 1, n) scanf("%s", str[i] + 1);
else
{
fo(i, 1, n)
{
scanf("%s", tmp + 1);
fo(j, 1, m) str[j][i] = tmp[j];
}
swap(n, m);
}
switch (n)
{
case 1:
{
printf("0\n");
break;
}
case 2:
{
work2();
break;
}
case 3:
{
work3();
break;
}
}
return 0;
}
解法二
自己写