2019-2020 ACM-ICPC Pacific Northwest Regional Contest (Div. 1)
2019-2020 ACM-ICPC Pacific Northwest Regional Contest (Div. 1)
https://codeforces.com/gym/102433
通过ABCDEIKLM
剩下的题应该会继续补,但是国外省赛题目质量感觉也堪忧啊,GM题看不懂,K n^2过2e5,见提交
tag
A 换根dp,B 栈、构造,C 01最短路,DE 水题,I 二分图最大独立集,K 离线线段树区间赋值, LM 搜索
A. Radio Prize
题意:给一棵树,有边权、点权,设两点间dis(x,y)为两端点点权和乘上两点间边权和,求所有点到i的dis和,i是1~n所有点。
就考虑一下换根怎么进行即可,首先dfs1跑一次求出1到所有点的dis和,然后dfs2对每条边换根。最后的答案一定是i的点权乘路径和加上一个值x,这个x实际上:对于每条边,它对x的贡献为它的所有子节点的权值和乘上该边的边权。所以要维护的值就比较显然了。注意这个题有点不好处理的地方实际上是因为它需要维护边权的值,所以记录的可能需要记录他的边而不是点的信息。一个是\(dp_i\)表示i这棵子树所有的边对答案的贡献,\(sum_i\)表示i这棵子树的点权和,\(num_i\)表示i这棵子树的点数,\(sum2_i\)表示i这颗子树到i这个点所有的边的边权和。
那么i为根的答案为\(dp_i+sum2_i*a_i\)
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/13
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int n;
int a[maxn];
ll sum[maxn];
ll dp[maxn];
ll sum2[maxn];
ll num[maxn];
ll res[maxn];
vector<pair<int, int> > edge[maxn];
void dfs1(int u, int pre) {
sum[u] = a[u];
dp[u] = 0;
num[u] = 1;
sum2[u] = 0;
for (auto i : edge[u]) {
int v = i.first;
if (v == pre) continue;
int w = i.second;
dfs1(v, u);
sum2[u] += sum2[v] + 1ll * w * num[v];
dp[u] += 1ll * sum[v] * w + dp[v];
sum[u] += sum[v];
num[u] += num[v];
}
}
void dfs2(int u, int pre) {
res[u] = dp[u] + 1ll * sum2[u] * a[u];
for (auto i : edge[u]) {
int v = i.first;
if (v == pre) continue;
int w = i.second;
dp[u] -= 1ll * w * sum[v] + dp[v];
sum[u] -= sum[v];
sum[v] += sum[u];
dp[v] += 1ll * w * sum[u] + dp[u];
sum2[u] -= sum2[v] + 1ll * w * num[v];
num[u] -= num[v];
num[v] += num[u];
sum2[v] += sum2[u] + 1ll * w * num[u];
dfs2(v, u);
dp[v] -= 1ll * w * sum[u] + dp[u];
sum[v] -= sum[u];
sum[u] += sum[v];
dp[u] += 1ll * w * sum[v] + dp[v];
sum2[v] -= sum2[u] + 1ll * w * num[u];
num[v] -= num[u];
num[u] += num[v];
sum2[u] += sum2[v] + 1ll * w * num[v];
}
}
int main(int argc, char* argv[]) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i < n; ++i) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
edge[u].push_back({v, w});
edge[v].push_back({u, w});
}
dfs1(1, 0);
dfs2(1, 0);
for (int i = 1; i <= n; ++i) printf("%lld\n", res[i]);
return 0;
}
B. Perfect Flush
题意:给一个序列,求其中最小字典序的子序列恰好包含1~k的所有数
考虑维护一个栈,当要考虑需不需要加入一个数进序列的时候,如果当前数小于栈底,需要考虑栈底是否在当前数之后还出现过,如果出现过显然可以贪心的把栈底推掉,如果大于则可以考虑先直接加进栈
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/13
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int n, k;
int a[maxn];
int vis[maxn];
int lst[maxn];
int main(int argc, char* argv[]) {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
memset(lst, -1, sizeof lst);
for (int i = n; i >= 1; --i) {
if (lst[a[i]] == -1) lst[a[i]] = i;
}
vector<int> x;
for (int i = 1; i <= n; ++i) {
if (a[i] > k || vis[a[i]]) continue;
if (x.empty()) x.push_back(a[i]), vis[a[i]] = 1;
else {
int j = x.size() - 1;
while (j >= 0) {
if (a[i] <= x[j] && i < lst[x[j]]) {
vis[x[j]] = 0;
j --;
}
else break;
}
x.resize(j + 1);
vis[a[i]] = 1;
x.push_back(a[i]);
}
}
for (auto i : x) printf("%d ", i);
return 0;
}
C. Coloring Contention
题意:给一张无向图,定义边有两种颜色,如果一条路径上两条连续的边颜色不同则称作是一次"color change",求最大化最小的"color change"次数
显然是最短路距离-1?其实并不会证明,但是能ac
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/13
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 50;
const int mod = 1e9 + 7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
vector<int> edge[maxn];
int dp[maxn];
int main(int argc, char *argv[]) {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 0, u, v; i < m; ++i) {
scanf("%d%d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
queue<int> qu;
memset(dp, -1, sizeof dp);
dp[1] = 0;
qu.push(1);
while (!qu.empty()) {
int tmp = qu.front();
qu.pop();
for (auto v : edge[tmp]) {
if (dp[v] == -1) {
dp[v] = dp[tmp] + 1;
qu.push(v);
}
}
}
printf("%d\n", dp[n] - 1);
return 0;
}
D. Dividing by Two
题意:有两种操作,1是如果当前为偶数则可以把它除二,2是加一,问把A变成B最少多少步
喔,有入门呐味了
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/13
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 10;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int main(int argc, char* argv[]) {
int n, m;
scanf("%d%d", &n, &m);
int res = 0;
while (n > m) {
res ++;
if (n & 1) n ++;
else n /= 2;
}
res += m - n;
printf("%d\n", res);
return 0;
}
E. Rainbow Strings
题意:给一个串,计数子序列中不包含相同字母
喔,太友好啦
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/13
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+50;
const int mod = 11092019;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int c[26];
int main(int argc, char* argv[]) {
string s;
cin >> s;
for (auto i : s) {
c[i - 'a'] ++;
}
ll res = 1;
for (int i = 0; i < 26; ++i) {
res = res * (c[i] + 1) % mod;
}
printf("%lld\n", res);
return 0;
}
I. Error Correction
题意:给n个串,保证n个串都是某些相同字母的排列(字母不重复且串长相等),求不会经过一次转换(交换任意两个位置的字符)得到相同串的最大独立集大小。
很精妙的题,考虑要从s1转换为s2,以s2的字典顺序做基准
比如
s1 = abc
s2 = cab
把s2看作123
s1则为231
那么s1的逆序对个数设为x,有一个well-known的结论是交换排列中任何一对点都会使排列的逆序对个数的奇偶性发生改变。
要证明的话,假设交换i和j位置上的数(不妨设i<j
),首先可知对i之前和j之后的点没有影响,然后x1表示ij之间小于i的个数,x2表示大于j的个数,那么
原先的逆序对数为 x1+x2
交换后的逆序对数为 2*(j-i+1)-x1-x2
改变量为 2(j-i+1)-(x1+x2)2 为偶数
当然不要忘记ij本身会产生/消除一个逆序对,所以改变量就成了奇数
所以,s1如果要变成s2,他的步数要么都是奇数,要么都是偶数,也就是说s1变成s2的步数的奇偶性是一致的。
所以考虑对每个串建点,如果串能一步到达则建边的话,这个图是不存在奇环的(因为点对之间的路径长度一定非奇即偶,他们的两倍一定是偶数)。那么就成了求这张图的最大独立集,也就是二分图最大匹配问题。
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/13
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 505;
const int mod = 1e9 + 7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
char s[maxn][30];
int len;
int n;
int edge[maxn][maxn];
int linker[maxn];
bool vis[maxn];
bool path(int u) {
for (int v = 1; v <= n; v++) {
if (edge[u][v] && !vis[v]) {
vis[v] = true;
if (linker[v] == -1 || path(linker[v])) {
linker[v] = u;
return true;
}
}
}
return false;
}
int hungary() {
int res = 0;
memset(linker, 0xff, sizeof(linker));
for (int i = 1; i <= n; i++) {
memset(vis, false, sizeof(vis));
res += path(i);
}
return res;
}
vector<int> G[maxn];
int c[maxn];
void dfs(int u) {
for (auto v : G[u]) {
if (c[v] == -1) {
c[v] = !c[u];
dfs(v);
}
}
}
vector<pair<int, int> > ss;
int main(int argc, char *argv[]) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%s", s[i]);
}
len = strlen(s[1]);
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
int tmp = 0;
for (int k = 0; k < len; ++k) {
if (s[i][k] != s[j][k]) {
tmp++;
}
if (tmp > 2) break;
}
if (tmp == 2) {
G[i].push_back(j);
G[j].push_back(i);
ss.emplace_back(i, j);
}
}
}
memset(c, -1, sizeof c);
for (int i = 1; i <= n; ++i) {
if (c[i] == -1) {
c[i] = 1;
dfs(i);
}
}
for (auto i : ss) {
if (c[i.first] == 0) {
edge[i.first][i.second] = 1;
} else edge[i.second][i.first] = 1;
}
printf("%d\n", n - hungary());
return 0;
}
K. Computer Cache
题意:给m个序列,一个长度为n的数组,三种操作,一是将某个序列从数组的某个位置开始整体copy,保证不会超过数组的长度,二是询问数组当前某个点的值,三是给某个序列某段区间加一。
如果不考虑三操作,可以做一个区间赋值和单点询问的操作,就是记录每个点是包含在哪个序列以哪个位置开头即可,比如将一个长度为x的序列赋值到p的位置,就相当于给\([p,p+x-1]\)这段位置赋值\((i,p)\),i表示赋值序列的下标,然后查询的时候现在数组的线段树上查询当前需要在哪个序列上查询哪个位置的值即可。然后考虑怎么把区间加操作也带上,一种显然的想法是对序列也维护线段树,但是会发现如果先赋值,再加,再询问的话需要考虑一个历史版本问题,所以考虑离线二三操作,二操作记录是哪一次赋值导致的查询,然后跟三操作一起排序就可以了。能做到永远先区间加再区间赋值单点询问就对了。代码改来改去写的比较挫。
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/24
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int len[maxn], st[maxn];
int x[maxn];
int le, re, k1, k2;
int idx[maxn << 2];
void pushdown(int rt) {
if (idx[rt] != 0) {
idx[rt << 1] = idx[rt];
idx[rt << 1 | 1] = idx[rt];
idx[rt] = 0;
}
}
void update(int rt, int l, int r) {
if (le <= l && r <= re) {
idx[rt] = k1;
return;
}
int mid = l + r >> 1;
pushdown(rt);
if (le <= mid) update(rt << 1, l, mid);
if (re > mid) update(rt << 1 | 1, mid + 1, r);
}
int query(int rt, int l, int r) {
if (idx[rt]) return idx[rt];
if (l == r) return idx[rt];
int mid = l + r >> 1;
if (le <= mid) return query(rt << 1, l, mid);
else return query(rt << 1 | 1, mid + 1, r);
}
int val[maxn << 2];
void Update(int rt, int l, int r) {
if (le <= l && r <= re) {
val[rt] ++;
return;
}
int mid = l + r >> 1;
if (le <= mid) Update(rt << 1, l, mid);
if (re > mid) Update(rt << 1 | 1, mid + 1, r);
}
int Query(int rt, int l, int r) {
if (l == r) {
return val[rt];
}
int mid = l + r >> 1;
if (le <= mid) return val[rt] + Query(rt << 1, l, mid);
else return val[rt] + Query(rt << 1 | 1, mid + 1, r);
}
pair<int, int> realpos[maxn];
struct node {
int idx, a;
int realidx;
};
int ans[maxn];
int main(int argc, char* argv[]) {
int n, m, q;
scanf("%d%d%d", &n, &m, &q);
int tot = 1;
for (int i = 1; i <= m; ++i) {
st[i] = tot;
scanf("%d", &len[i]);
tot += len[i];
for (int j = st[i]; j < st[i] + len[i]; ++j) {
scanf("%d", &x[j]);
}
}
// printf("tot - 1: %d\n", tot - 1);
// printf("x : ");
// for (int i = 1; i < tot; ++i) {
// printf("%d ", x[i]);
// }
// printf("\n");
vector<node> Q, A;
for (int j = 1; j <= q; ++j) {
int op;
scanf("%d", &op);
if (op == 1) {
int i, pos;
scanf("%d%d", &i, &pos);
le = pos, re = pos + len[i] - 1;
realpos[j] = {i, pos};
k1 = j;
// printf("Change %d %d to (%d)\n", le, re, k1);
update(1, 1, n);
} else if (op == 2) {
int pos;
scanf("%d", &pos);
le = pos;
auto tmp = query(1, 1, n);
if (tmp == 0) {
Q.push_back({0, 0, j});
} else {
ans[j] = x[st[realpos[tmp].first] + (pos - realpos[tmp].second)];
Q.push_back({tmp, st[realpos[tmp].first] + (pos - realpos[tmp].second), j});
}
} else {
int i, l, r;
scanf("%d%d%d", &i, &l, &r);
int pos = st[i];
le = pos + l - 1, re = pos + r - 1;
// printf("Add %d %d\n", le, re);
A.push_back({j, pos + l - 1, pos + r - 1});
// Update(1, 1, tot - 1);
}
}
sort(Q.begin(), Q.end(), [](node a, node b) {
return a.idx < b.idx;
});
int pos = 0;
while (pos < Q.size() && Q[pos].idx == 0) pos ++;
for (int i = 0; i < A.size(); ++i) {
if (pos == Q.size()) break;
while (pos < Q.size() && Q[pos].idx < A[i].idx) {
le = Q[pos].a;
ans[Q[pos].realidx] += Query(1, 1, tot - 1);
pos ++;
}
le = A[i].a, re = A[i].realidx;
Update(1, 1, tot - 1);
}
while (pos < Q.size()) {
le = Q[pos].a;
ans[Q[pos].realidx] += Query(1, 1, tot - 1);
pos ++;
}
sort(Q.begin(), Q.end(), [](node a, node b) {
return a.realidx < b.realidx;
});
for (auto i : Q) {
printf("%d\n", ans[i.realidx] % 256);
}
return 0;
}
L. Carry Cam Failure
题意:定义不进位乘法,求是否存在整数根号,求最小解。所谓不进位乘法就是a对于b的每一位相乘时不进位,最后每位加起来也不进位。
比如17×17
17
17
------
79
17
------
149
询问的数不超过25位
大概就直接搜索剪枝,对于每一位合法的解不会很多,从0开始枚举即可。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/15
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 30;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int digres[maxn];
int dig[maxn];
int need[maxn];
//int p10[maxn] = {1};
int calnow[maxn];
int pos;
//int getpw() {
// memset(digres, 0, sizeof digres);
// for (int i = 0; i < pos; ++i) {
// for (int j = 0; j < pos; ++j) {
// if (i == 0) digres[j] = (dig[i] * dig[j]) % 10;
// else digres[i + j] += (dig[i] * dig[j]) % 10;
// }
// }
// for (int i = 0; i < pos * 2; ++i) {
// if (need[i] != digres[i] % 10) return false;
// }
// return true;
//}
//int pw(int x) {
// int pos = 0;
// int it = x;
// while (it > 0) {
// dig[pos ++] = it % 10;
// it /= 10;
// }
// int res = 0;
// memset(digres, 0, sizeof digres);
// for (int i = 0; i < pos; ++i) {
// for (int j = 0; j < pos; ++j) {
// if (i == 0) digres[j] = (dig[i] * dig[j]) % 10;
// else digres[i + j] += (dig[i] * dig[j]) % 10;
// }
// }
// for (int i = 0; i < pos * 2; ++i) {
// res += digres[i] % 10 * p10[i];
// }
// return res;
//}
int mp[10][10];
char s[maxn];
int flag;
void dfs(int dep) {
if (dep == -1) {
flag = 1;
for (int i = pos - 1; i >= 0; --i) {
if (calnow[i] % 10 != need[i]) {
flag = 0;
return;
}
}
// if (calnow[0] % 10 == need[0]) flag = 1;
// if (getpw()) flag = 1;
return;
}
if (flag) return;
for (int i = 0; i < 10; ++i) {
if (flag) return;
dig[dep] = i;
if ((dep == pos - 1 && dig[dep] * dig[dep] % 10 == need[dep * 2]) || (dep != pos -1 &&
(calnow[dep + pos - 1] + dig[dep] * dig[pos - 1] * 2) % 10 == need[dep + pos - 1])) {
for (int j = pos - 1; j > dep; --j) calnow[j + dep] += 2 * (dig[dep] * dig[j]) % 10;
calnow[dep * 2] += dig[dep] * dig[dep] % 10;
dfs(dep - 1);
if (flag) return;
for (int j = pos - 1; j > dep; --j) calnow[j + dep] -= 2 * (dig[dep] * dig[j]) % 10;
calnow[dep * 2] -= dig[dep] * dig[dep] % 10;
}
// int tmp = (i * i % 10 == );
// for (int j = dep + 1; j < pos; ++j) {
// if (dig[j] * i % 10 != need[j + dep]) {
// tmp = 0;
// break;
// }
// }
// if (tmp) {
// }
}
}
int main(int argc, char* argv[]) {
// for (int i = 1; i < maxn; ++i) p10[i] = p10[i - 1] * 10;
for (int i = 1; i < 10; ++i) {
for (int j = 1; j < 10; ++j) {
mp[i][j] = i * j % 10;
}
}
scanf("%s", s);
int len = strlen(s);
if (len % 2 == 0) {
puts("-1");
exit(0);
}
for (int i = 0; i < len; ++i) {
need[i] = s[len - i - 1] - '0';
}
pos = (len + 1) / 2;
dfs(pos - 1);
if (flag) {
for (int i = pos - 1; i >= 0; --i) printf("%d", dig[i]);
printf("\n");
} else puts("-1");
return 0;
}
M. Maze Connect
题意:其实描述的并不清楚?问被包围的.
有多少个,如果是
/\
\/
这样的形状也算一个
但是
.\\
/.\
\//
这样的其实题目并没有清楚的定义算不算
虽然ac了但是这个题没有什么做的价值
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/15
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e3+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int n, m;
int xc[] = {1, 0, -1, 0};
int yc[] = {0, 1, 0, -1};
char s[maxn][maxn];
bool check(int x, int y) {
return x >= 1 && y >= 1 && x <= n && y <= m;
}
void bfs(int x, int y) {
queue<pair<int, int> > qu;
qu.emplace(x, y);
s[x][y] = '-';
while (!qu.empty()) {
x = qu.front().first;
y = qu.front().second;
qu.pop();
for (int i = 0; i < 4; ++i) {
int xx = x + xc[i];
int yy = y + yc[i];
if (check(xx, yy) && s[xx][yy] == '.') {
s[xx][yy] = '-';
qu.emplace(xx, yy);
}
}
if (x > 1 && y < m && !(s[x][y + 1] == '\\' && s[x - 1][y] == '\\') && s[x - 1][y + 1] == '.')
s[x - 1][y + 1] =
'-', qu.emplace(x - 1, y + 1);
if (x < n && y < m && !(s[x][y + 1] == '/' && s[x + 1][y] == '/') && s[x + 1][y + 1] == '.')
s[x + 1][y + 1] =
'-', qu.emplace(x + 1, y + 1);
if (x > 1 && y > 1 && !(s[x - 1][y] == '/' && s[x][y - 1] == '/') && s[x - 1][y - 1] == '.')
s[x - 1][y - 1] =
'-', qu.emplace(x - 1, y - 1);
if (x < n && y > 1 && !(s[x + 1][y] == '\\' && s[x][y - 1] == '\\') && s[x + 1][y - 1] == '.')
s[x + 1][y - 1] =
'-', qu.emplace(x + 1, y - 1);
}
}
int main(int argc, char* argv[]) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%s", s[i] + 1);
int res = 0;
for (int i = 1; i < n; ++i) {
for (int j = 1; j < m; ++j) {
if (s[i][j] == '/' && s[i][j + 1] == '\\' && s[i + 1][j] == '\\' && s[i + 1][j + 1] == '/') {
res ++;
}
}
}
for (int i = 1; i <= n; ++i) {
if (s[i][1] == '.') bfs(i, 1);
if (s[i][m] == '.') bfs(i, m);
}
for (int i = 1; i <= m; ++i) {
if (s[1][i] == '.') bfs(1, i);
if (s[n][i] == '.') bfs(n, i);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (s[i][j] == '.') {
res ++;
bfs(i, j);
}
}
}
printf("%d\n", res);
return 0;
}