您的位置:首页 > 其它

【解题报告】BestCoder Round #77 (div.2)

2016-04-21 15:44 363 查看
题目链接

A.so easy(HDU5650)

思路

假设集合为 {1,2,3}\{1, 2, 3\} ,那么集合的全部子集为 {},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}\{\}, \{1\}, \{2\}, \{3\}, \{1, 2\}, \{2, 3\}, \{1, 3\}, \{1, 2, 3\} 。我们要将所有子集的元素全部异或起来,于是想到统计每种元素在全部子集中出现的个数。例如, 11 出现的次数为 44 , 22 出现的次数为 44 , 33 出现的次数为 44 ……等等,这是不是一种巧合?还是原本就能直接算出每种元素出现的次数?我们以 11 为例计算其出现的次数:当子集只有 11 个元素的时候出现 11 次,当子集有 22 个元素的时候出现 C12C_2^1 次,因为要找另一个数与其组合,当子集有 33 个元素的时候出现 11 次。这样,元素 11 出现的次数就是 1+C12+1=41 + C_2^1 + 1 = 4 。事实上,当集合中有 nn 个元素时,每个元素出现的次数都是 C0n−1+C1n−1+C2n−1+……+Cn−1n−1=2n−1C_{n-1}^0 + C_{n-1}^1 + C_{n-1}^2+ …… + C_{n-1}^{n-1} = 2^n - 1 。至此可以得出结论当 n>1n > 1 时,每种元素出现的次数都是偶数,于是最后的异或总和为 00 ,当 n=1n = 1 时最后的异或和就是那个数本身。

代码

#include <cstdio>

int t, n, a;

int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%d", &a);
}
printf("%d\n", n == 1 ? a : 0);
}
return 0;
}


B.xiaoxin juju needs help(HDU 5651)

思路

要将字符串的字母重新排列得到回文串,那么这个字符串的顺序就不是我们关系的。我们关心的应该是每个字母的数量有多少个。既然要组成回文串,那么数量为奇数的字母只能有1种(排在回文串的最中间)。满足这个条件后就可以计算能够构成多少个回文串了。这个问题等价于每种字母用一半,能够构造出多少种排列。假设所有的字母的数量都为偶数且 a,b,c,...,za, b, c, ..., z 的数量的一半分别为 n1,n2,n3,...,n26n_1, n_2, n_3, ..., n_{26} ,它们的和的一半为 nn ,那么答案就应该是 ans=nn1!n2!n3!...n26!ans = \frac {n}{n_1!n_2!n_3!...n_{26}!} 。由于要对结果取模,且 abmodm≠amodmbmodm\frac{a}{b} \bmod m \neq \frac{a \bmod m}{b \bmod m} 而 abmodm=(a×b′)modm\frac{a}{b} \bmod m = (a \times b') \bmod m ,其中 b′b' 为 modm\bmod m 下 bb 的逆元。因为 m=1000000007m = 1000000007 是个质数,所以可以用费马小定理求模的逆元。

代码

#include <cstdio>
#include <cstring>

typedef long long ll;
const int maxn = 1010;
const ll mod = 1e9 + 7;
char s[maxn];
int t, n, cnt, G[30];
ll ans, f[maxn], inv[maxn];

// 快速幂函数
ll modPow(ll a, ll n, ll mod) {
ll ans = 1;
for(; n > 0; n >>= 1) {
if(n & 1) ans = (ans * a) % mod;
a = (a * a) % mod;
}
return ans;
};

int main() {
f[0] = 1;
// 预处理出阶乘
for(int i = 1; i <= 1000; i++) {
f[i] = ((ll)i * f[i-1]) % mod;
}
// 预处理出模逆元
for(int i = 1; i <= 1000; i++) {
inv[i] = modPow(f[i], mod - 2, mod);
}
scanf("%d", &t);
while(t--) {
scanf("%s", s);
n = strlen(s);
memset(G, 0, sizeof(G));
for(int i = 0; i < n; i++) {
G[s[i]-'a']++;
}
cnt = 0;
for(int i = 0; i < 26; i++) {
cnt += (G[i] % 2);
}
if(cnt > 1) {
ans = 0;
}
else {
ans = f[n/2];
for(int i = 0; i < 26; i++) {
if(G[i] > 1) {
ans = (ans * inv[G[i]/2]) % mod;
}
}
}
printf("%I64d\n", ans);
}
return 0;
}


C.India and China Origins(HDU 5652)

思路

中国和印度之间的沟通完全中断当且仅当网格的左端与网格的右端通过“山”完全连通。那么我们可以用并查集维护连通分量并对网格左端和网格右端分别建立建立虚拟节点,每当一座山出现的时候,让这座山和它周围的山的连通分量合并。接着查询,当网格左端与网格右端的虚拟结点连通的年份就是最后的答案。

代码

#include <bits/stdc++.h>
using namespace std;

// 储存八个方向坐标变化的数组
const int dx[] = {-1, -1, 0, 1, 1, 1, 0, -1};
const int dy[] = {0, -1, -1, -1, 0, 1, 1, 1};
const int maxn = 510, maxp = 25e4 + 10;
char s[maxn];
int t, n, m, q, l, r, ans;
int x[maxp], y[maxp], p[maxp], G[maxn][maxn];

// 并查集的查找
int Find(int x) {
return p[x] == x ? x : p[x] = Find(p[x]);
}

// 并查集的合并
void Union(int x, int y) {
x = Find(x);
y = Find(y);
if(x != y) {
p[x] = y;
}
}

// 合并周围的连通分量
void connect(int x, int y) {
if(G[x][y] == 0) {
return;
}
// 网格左端联系左端虚拟结点
if(y == 0) {
Union(l, x * m + y);
}
// 网格右端联系右端虚拟结点
if(y == m - 1) {
Union(r, x * m + y);
}
// 检查周围八个格子
for(int i = 0; i < 8; i++) {
int a = x + dx[i];
int b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && G[a][b]) {
Union(x * m + y, a * m + b);
}
}
}

int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &m);
// 建立虚拟结点
l = n * m;
r = n * m + 1;
// 初始化并查集
for(int i = 0; i <= r; i++) {
p[i] = i;
}
for(int i = 0; i < n; i++) {
scanf("%s", s);
for(int j = 0; j < m; j++) {
G[i][j] = (s[j] == '1');
}
}
scanf("%d", &q);
for(int i = 1; i <= q; i++) {
scanf("%d%d", &x[i], &y[i]);
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
connect(i, j);
}
}
// 判断是否完全阻断
if(Find(l) == Find(r)) {
puts("0");
continue;
}
ans = -1;
for(int i = 1; i <= q; i++) {
G[x[i]][y[i]] = 1;
connect(x[i], y[i]);
// 判断是否完全阻断
if(Find(l) == Find(r)) {
ans = i;
break;
}
}
printf("%d\n", ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: