A: 暴力
B: 暴力
C: dp,可以暴力,也可以不暴力
D: 拓扑排序,dfs,方案计数
E: 数学,有意思
比赛链接:Codeforces Round #369 (Div2)
A. Bus to Udayland
题意:
给一个
n
排的公交车座位,每排有四个位置,左右各两个,中间是过道,空的座位用O表示,非空的座位用X表示。要从中找到相邻的两个空的座位并输出。
数据范围:
分析:
暴力扫即可。
时间复杂度: O(n)
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 1010;
int n;
char s[MAX_N][10];
int main()
{
while (~scanf("%d", &n)) {
int flag = 0;
for(int i = 0; i < n; ++i) {
scanf("%s", s[i]);
if (flag) continue;
if (s[i][0] == 'O' && s[i][1] == 'O') {
s[i][0] = s[i][1] = '+';
flag = 1;
} else if (s[i][3] == s[i][4] && s[i][3] == 'O') {
s[i][3] = s[i][4] = '+';
flag = 1;
}
}
if (flag == 0) printf("NO\n");
else {
printf("YES\n");
for (int i = 0; i < n; ++i) {
printf("%s\n", s[i]);
}
}
}
return 0;
}
B. Chris and Magic Square
题意:
给一个
n∗n
的矩阵,只有一个位置的元素是未知的用0表示,其余位置都是正整数,要求将未知的位置填上一个正整数
X
,使得每一行,每一列,主对角线和副对角线的元素之和都相等。输出
数据范围: n≤500,data[i][j]≤109
分析:
暴力扫求和,然后判断即可。注意开long long
。
时间复杂度: O(n2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 510;
int n;
ll mat[MAX_N][MAX_N], r[MAX_N], c[MAX_N], a, b, x, y;
void solve()
{
if (n == 1) {
printf("1\n");
return ;
}
ll ans;
if (x == n) ans = r[n - 1] - r[n];
else ans = r[x + 1] - r[x];
c[y] += ans;
r[x] += ans;
if (x == y) a += ans;
if (x == n - y + 1) b += ans;
int flag = 1;
if (a != b || ans < 0) flag = 0;
for (int i = 1; i <= n; ++i) {
if (a != r[i] || a != c[i]) {
flag = 0;
break;
}
}
if (flag && ans > 0) printf("%I64d\n", ans);
else printf("-1\n");
}
int main()
{
while (~scanf("%d", &n)) {
memset(mat, 0, sizeof(mat));
memset(r, 0, sizeof(r));
memset(c, 0, sizeof(c));
a = b = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
scanf("%I64d", &mat[i][j]);
if (i == j) a += mat[i][j];
if (i == n - j + 1) b += mat[i][j];
r[i] += mat[i][j];
c[j] += mat[i][j];
if (mat[i][j] == 0) {
x = i, y = j;
}
}
}
solve();
}
return 0;
}
C. Coloring Trees
题意:
给一排
n
棵树,每棵树初始时会被染色,用正整数表示,如果未被染色就是0.这一排树连续的一段相同颜色的树被称为一段。没被染色的树可以用
数据范围:
分析:
比赛时一直在想
O(n∗m∗K)
的dp
,差一点就写完了(捂脸.jpg)。赛后看到大佬们都是
O(n∗m2∗K)
的dp
,我的内心其实是崩溃的。
O(n∗m∗K)
的dp
我是用
dp[i][j]
表示将前
i
棵树分成
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
int n, m, K;
int color[MAX_N], id[MAX_N][MAX_N];
ll dp[MAX_N][MAX_N], dpp[MAX_N][MAX_N], w[MAX_N][MAX_N];
ll vis[MAX_N][MAX_N][MAX_N];
void update(int i, int j, int t, ll tmp)
{
if (tmp + w[i][t] < dp[i][j]) {
dpp[i][j] = dp[i][j];
dp[i][j] = tmp + w[i][t];
id[i][j] = t;
} else if (tmp + w[i][t] < dpp[i][j]) {
dpp[i][j] = tmp + w[i][t];
}
}
void solve()
{
memset(dp, 0x3f, sizeof(dp));
memset(vis, 0x3f, sizeof(vis));
memset(id, 0, sizeof(id));
if (color[1]) {
dp[1][1] = 0, id[1][1] = color[1];
vis[1][color[1]][1] = 0;
} else {
for (int t = 1; t <= m; ++t) {
vis[1][t][1] = w[1][t];
update(1, 1, t, 0);
}
}
ll tmp;
for (int i = 2; i <= n; ++i) {
if (color[i]) { // 当前颜色确定
for (int j = 1; j <= min(K, i); ++j) {
if (color[i - 1]) {
if (color[i] == color[i - 1]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = dp[i - 1][j - 1];
vis[i][color[i]][j] = dp[i][j];
} else {
if (id[i - 1][j - 1] == color[i]) tmp = dpp[i - 1][j - 1];
else tmp = dp[i - 1][j - 1];
if (id[i - 1][j] == color[i]) tmp = min(tmp, dp[i - 1][j]);
tmp = min(tmp, vis[i - 1][color[i]][j]); // 这个判断很重要!
vis[i][color[i]][j] = tmp;
update(i, j, 0, tmp);
}
id[i][j] = color[i];
}
} else { // 当前颜色不确定
for (int j = 1; j <= min(K, i); ++j) {
if (color[i - 1]) { // 前一颗树颜色确定
for (int t = 1; t <= m; ++t) {
if (t == color[i - 1]) {
tmp = dp[i - 1][j];
} else {
if (id[i - 1][j - 1] == t) tmp = dpp[i - 1][j - 1];
else tmp = dp[i - 1][j - 1];
if (id[i - 1][j] == t) tmp = min(tmp, dp[i - 1][j]);
}
tmp = min(tmp, vis[i - 1][t][j]); // 这个判断很重要!
vis[i][t][j] = tmp + w[i][t];
update(i, j, t, tmp);
}
} else { // 前一棵树颜色不确定
for (int t = 1; t <= m; ++t) { // 枚举当前树的颜色
if (id[i - 1][j - 1] == t) tmp = dpp[i - 1][j - 1];
else tmp = dp[i - 1][j - 1];
if (id[i - 1][j] == t) tmp = min(tmp, dp[i - 1][j]);
tmp = min(tmp, vis[i - 1][t][j]); // 这个判断很重要!
vis[i][t][j] = tmp + w[i][t];
update(i, j, t, tmp);
}
}
}
}
}
if (dp[n][K] == INF) dp[n][K] = -1;
printf("%I64d\n", dp[n][K]);
}
int main()
{
while (~scanf("%d%d%d", &n, &m, &K)) {
int pre = 0, cnt = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d", &color[i]);
if (color[i] && color[i] != pre) {
cnt ++;
}
pre = color[i];
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%I64d", &w[i][j]);
}
}
if (K > n || cnt > K) printf("-1\n");
else solve();
}
return 0;
}
O(n∗m2∗K)
的dp
就没什么好讲的了。不过这种dp
跑了300+ms,而上面的只有30+ms。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
int n, m, K;
int color[MAX_N];
ll w[MAX_N][MAX_N], dp[MAX_N][MAX_N][MAX_N];
void solve()
{
memset(dp, 0x3f, sizeof(dp));
if (color[1]) dp[1][color[1]][1] = 0;
else {
for (int j = 1; j <= m; ++j) {
dp[1][j][1] = w[1][j];
}
}
for (int i = 2; i <= n; ++i) {
for (int j = 1; j <= min(j, K); ++j) {
if (color[i]) {
for (int p = 1; p <= m; ++p) {
if (p == color[i]) dp[i][color[i]][j] = min(dp[i][color[i]][j], dp[i - 1][p][j]);
else dp[i][color[i]][j] = min(dp[i][color[i]][j], dp[i - 1][p][j - 1]);
}
} else {
for (int t = 1; t <= m; ++t) {
for (int p = 1; p <= m; ++p) {
if (p == t) dp[i][t][j] = min(dp[i][t][j], dp[i - 1][p][j] + w[i][t]);
else dp[i][t][j] = min(dp[i][t][j], dp[i - 1][p][j - 1] + w[i][t]);
}
}
}
}
}
ll ret = INF;
for (int i = 1; i <= m; ++i) {
ret = min(ret, dp[n][i][K]);
}
if (ret == INF) ret = -1;
printf("%I64d\n", ret);
}
int main()
{
while (~scanf("%d%d%d", &n, &m, &K)) {
for (int i = 1; i <= n; ++i) {
scanf("%d", &color[i]);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%I64d", &w[i][j]);
}
}
solve();
}
return 0;
}
D.Directed Roads
题意:
给一个
n
个点的有向图,每个点有且仅有一条边从它指出去。可以翻转一条边的方向:1e9+7
取模。
数据范围: n≤2∗105
分析:
这题让我想起了之前的一道题:Codeforces 699 D Fix a Tree有兴趣的可以看看。
要注意到几个事实。首先所有的有向环不会相交的,因为每个点只会伸出去一条边。其次,我们假设单链和指向环的边的个数为
t
,那么此时的方案数是
那么剩下的就简单了。我是用拓扑排序处理出
t
,然后用
时间复杂度: O(n)
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int MAX_N = 200010;
const ll mod = 1e9 + 7;
int n;
int to[MAX_N], degree[MAX_N], vis[MAX_N];
ll pw2[MAX_N];
queue<int> que;
void init()
{
pw2[0] = 1;
for (int i = 1; i < MAX_N; ++i) {
pw2[i] = pw2[i - 1] * 2 % mod;
}
}
int TopoSort()
{
int ret = 0;
while (!que.empty()) que.pop();
for (int i = 1; i <= n; ++i) {
if (degree[i] == 0) que.push(i);
}
while (!que.empty()) {
int cur = que.front();
que.pop();
ret++;
if (--degree[to[cur]] == 0) que.push(to[cur]);
to[cur] = 0;
}
// printf("TopoSort = %d\n", ret);
return ret;
}
void dfs(int u, int& depth)
{
if (to[u] == 0) return;
depth++;
int v= to[u];
to[u] = 0;
dfs(v, depth);
}
void wyr()
{
ll ret = pw2[TopoSort()];
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; ++i) {
if (to[i]) {
int d = 0;
dfs(i, d);
// printf("i = %d d = %d\n", i, d);
ret = ret * (pw2[d] - 2) % mod;
}
}
printf("%I64d\n", ret);
}
int main()
{
init();
while (~scanf("%d", &n)) {
memset(degree, 0, sizeof (degree));
for (int i = 1; i <= n; ++i) {
scanf("%d", &to[i]);
degree[to[i]]++;
}
wyr();
}
return 0;
}
E. ZS and The Birthday Paradox
题意:
有 2n 天(日期)和 k 个人,求至少有两个人的生日在同一天的概率?用分数表示。
数据范围:
分析:
把问题转化一下,先求一下反面:两两不同的概率。这个很简单: Ck2n(2n)k .
剩下的就比较有意思了。
- 你要想到把这个式子简化一下:
(2n−1)∗(2n−2)∗⋯∗(2n−(k−1))2n∗(k−1)
注意到分母只是2的幂,那么分子和分母的最大公约数也只能是2的幂。 - 你要想到当
2n−t(t<2n)
时找到最大的
p
使得
2p|(2n−t) 等价于找到最大的 p 使得2p|t 。
证明:假设:
2n−t=r∗2p
,其中
r
为奇数,此时
- 你要想到求分子的最大的2的幂次的因子相当于求 (k−1)! 的最大的2的幂次的因子。
- 你要想到上面一步是等于: ∑i=1k−12i
- 你要想到求出来分子分母的最大公约数后还要用到逆元(因为有模数)。
- 你要想到当 k≥2n 的时候答案恒为1/1。
- 你要想到因为分子是连续的一串数相乘,那么分别对mod取余后结果肯定也是连续的。当
k−1≥mod
时,取余的结果肯定会出现0,所以分子肯定是0.当
k−1≤mod
时,可以递推暴力算。因为mod只有
1e6+3
,而且是个素数。 - 你要想到分母的幂是
n∗(k−1)
是会爆
long long
的,你可以这样:quick_pow(quick_pow(2, n), k - 1)
。
或者根据费马小定理:
2mod−1≡mod(mod为素数时)
对幂次进行降幂: (n%(mod−1))∗((k−1)%(mod−1))%(mod−1) ,这样子就不会爆了。
时间复杂度: O(mod+log(k)+log(n))
好累。。。。。。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const ll mod = 1000003;
int num[mod + 10], sum[mod + 10];
ll quick_pow(ll a, ll b)
{
ll ret = 1, tmp = a;
while (b) {
if (b & 1) ret = ret * tmp % mod;
tmp = tmp * tmp % mod;
b >>= 1;
}
return ret;
}
ll work(ll x)
{
--x;
ll ret = 0, base = 2;
while (x / base) {
ret += (x / base);
base <<= 1;
}
return ret;
}
int main()
{
ll n, K;
while (~scanf("%I64d%I64d", &n, &K)) {
if (n <= 63 && K > (1LL << n)) {
printf("1 1\n");
continue;
}
ll base = quick_pow(2, n);
ll B = quick_pow(quick_pow(2, mod - 2), work(K)); // 逆元
ll A = 1; // numerator
if (K - 1 >= mod) A = 0;
else {
for (int i = 1; i <= K - 1; ++i) {
A = A * (base - i) % mod;
}
}
A = A * B % mod;
B = B * quick_pow(base, K - 1) % mod; // denominator
A = ((B - A) % mod + mod) % mod;
printf("%I64d %I64d\n", A, B);
}
return 0;
}