Codeforces Round #663 (Div. 2) 部分题解

2 篇文章 0 订阅
1 篇文章 0 订阅

C. Cyclic Permutations

题意

给定一个大小为 n n n的排列 { a i } \{a_i\} {ai},按照以下方式建图:

  • 对于每个满足 1 ≤ i ≤ n 1 \leq i \leq n 1in i i i,找到最大的 j j j,使得其满足 1 ≤ j < i 1 \leq j < i 1j<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 1in i i i,找到最小的 j j j,使得其满足 i < j ≤ n i < j \leq n i<jn 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 k3.
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) (1i<jk)
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(1i<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,,st1,at ( t − s + 1 ≥ 3 ) (t-s+1\geq3) (ts+13),排列中相邻两个数连一条边, 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,,st1,at ( t − s + 1 ≥ 3 ) (t-s+1\geq3) (ts+13),对于每个满足 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} 2n1,推导也不难。
对于一个大小为 n n n的排列,我们按照数从大到小的顺序考虑,对于满足 1 ≤ i < n 1\leq i <n 1i<n i i i,可以选择放在 i + 1 i+1 i+1的左边或右边,因此总排列数为 2 n − 1 2^{n-1} 2n1
所以,最终的答案是 n ! − 2 n − 1 n!-2^{n-1} n!2n1

代码

#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 44的矩阵和为奇数,根据定义,该 4 ∗ 4 4*4 44的所有 2 ∗ 2 2*2 22的子矩阵和为奇数,而 4 ∗ 4 4*4 44的矩阵可以拆成 4 4 4 2 ∗ 2 2*2 22子矩阵和之和,该 4 ∗ 4 4*4 44的矩阵和为偶数,与开始时矩阵和为奇数矛盾。
现在假定 n ≤ m n\leq m nm,(若 n > m n>m n>m可以翻转该矩阵),对 n n n进行分类讨论:

  1. n = 1 n=1 n=1,答案为 0 0 0
  2. n = 2 n=2 n=2,我们将每一列的两个数求和,得出一个长度为 m m m的序列,最终“好矩阵”对应的序列肯定是奇偶交替的(即:奇偶奇偶 ⋯ \cdots ,或偶奇偶奇 ⋯ \cdots ),两种情况求最小值即可。
  3. n = 3 n=3 n=3,我们分别将每一列的前两个数求和,后两个数求和,得出一个 2 ∗ m 2*m 2m的序列,最终“好矩阵”肯定是以下四种情况之一:
123 ⋯ \cdots
⋯ \cdots
⋯ \cdots
123 ⋯ \cdots
⋯ \cdots
⋯ \cdots
123 ⋯ \cdots
⋯ \cdots
⋯ \cdots
123 ⋯ \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 i1列的数的 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 22子矩阵和是否为奇数)。

代码

解法一

#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;
}

解法二

自己写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值