第三个专题了,初期数据结构:
(1)、串
1、poj1035
题意:给定一个字典,再给你一些字符串,如果字典中有这个字符串,则直接输出,如果没有的话,那就找字符串加一个字符或少一个字符或者换一个字符是否可以在字典中找到相应的字符串。
分析:水题,直接暴力。。。
#include <cstdio>
#include <cstring>
char s[10010][20], str[60][20], temp[20];
void Replace(char s[][20], char str[][20], int snum, int strnum) {
int Len, len, tag, flag, Tag;
int i, j, x, y;
for (y = 0; y < strnum; y++) {
flag = 1;
for (i = 0; i < snum; i++) {
if (strcmp(s[y], str[i]) == 0) {
flag = 0;
printf("%s is correct\n", s[y]);
break;
}
}
if (!flag) continue;
printf("%s:", s[y]);
for (i = 0; i < snum; i++) {
len = strlen(s[y]);
Len = strlen(str[i]);
if (len == Len) {
tag = 0;
for (x = 0; x < len && tag != 2; x++) {
if (str[i][x] != s[y][x]) tag++;
}
if (tag == 1) printf(" %s", str[i]);
continue;
}
if (Len+1 == len || Len == len+1) {
int min = Len>len?len:Len;
int max = Len>len?Len:len;
flag = 1;
for (x = 0; x < min && flag; x++) {
if (s[y][x] != str[i][x]) flag = 0;
}
if (flag) printf(" %s", str[i]);
else {
for (x = 0; x < min; x++) {
tag = Tag = 1;
for (j = 0; j < max && tag; j++) {
if (j == x) {
Tag = 0;
continue;
}
if (Tag) {
if (str[i][j] != s[y][j]) tag = 0;
}
else {
if (Len < len) {
if (str[i][j-1] != s[y][j]) tag = 0;
}
else {
if (str[i][j] != s[y][j-1]) tag = 0;
}
}
}
if (tag) {
printf(" %s", str[i]);
break;
}
}
}
}
}
puts("");
}
}
int main() {
int i = 0, j = 0, x, y;
while (scanf("%s", temp), strcmp("#", temp) != 0) strcpy(s[i++], temp);
while (scanf("%s", temp), strcmp("#", temp) != 0) strcpy(str[j++], temp);
Replace(str, s, i, j);
return 0;
}
2、poj3080
题意:求n个字符串的最长公共串。
分析:直接暴力,水题。。。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 70;
const int LEN = 60;
char s[15][N], str[N], ans[N];
bool check(int n) {
for (int i = 1; i < n; i++)
if (!strstr(s[i], str)) return 0;
return 1;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%s", s[i]);
memset(ans, 0, sizeof(ans));
int ma = 0;
for (int i = 0; i <= LEN - ma; i++) {
int t = ma;
copy(s[0]+i, s[0]+i+t, str);
str[t] = 0;
for (int j = i+t; j <= LEN; j++) {
if (check(n)) {
if ((t == ma && strcmp(str, ans) < 0) || t > ma) strcpy(ans, str), ma = t;
}
else break;
str[t] = s[0][j]; str[++t] = 0;
}
}
puts(strlen(ans) < 3 ? "no significant commonalities" : ans);
}
return 0;
}3、poj1936
题意:求一个串是不是包含于另一个串中,且相对位置不变。
分析:直接暴力,复杂度最多为O(strlen(s1)+ strlen(s2))。
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int N = 100005;
char s[N], str[N];
int len;
bool cal(int &pos, int i) {
for (; pos < len; pos++)
if (s[pos] == str[i]) { pos++; return 1; }
return 0;
}
int main() {
while (~scanf("%s %s", str, s)) {
len = strlen(s);
int p = 0, l = strlen(str);
if (l > len) { puts("No"); continue; }
if (l == len) { puts(strcmp(s, str) == 0 ? "Yes" : "No"); continue; }
int flag = 1;
for (int i = 0; i < l && flag; i++)
if (p == len || !cal(p, i)) flag = 0;
puts(flag ? "Yes" : "No");
}
return 0;
}(2)、排序
1、poj2388
题意:求一个序列最中间的数。
分析:nth_element,复杂度为O(n),不需要给数组排序。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10005;
int a[N];
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
nth_element(a, a+n/2, a+n);
printf("%d\n", a[n/2]);
return 0;
}2、poj2299
题意:说那么多,就是求逆序数。
分析:不能暴力,树状数组和归并排序都可以,然而我这种蒟蒻不会树状数组。。。
#include <cstdio>
#include <algorithm>
#include <cctype>
using namespace std;
typedef long long LL;
const int N = 500005;
int a[N], b[N];
template <class T>
inline void read(T &res) {
char c; res = 0;
while (!isdigit(c = getchar()));
while (isdigit(c)) res = res * 10 + c - '0', c = getchar();
}
LL Merge(int* a, int l, int r) {
if (l >= r) return 0;
int m = (l + r) / 2;
LL cnt = Merge(a, l, m);
cnt += Merge(a, m + 1, r);
int i = l, j = m + 1, x = 0, y;
while (i <= m && j <= r) {
if (a[i] <= a[j]) b[x++] = a[i++];
else b[x++] = a[j++], cnt += m - i + 1;
}
if (i <= m) copy(a + i, a + m + 1, b + x);
else if (j <= r) copy(a + j, a + r + 1, b + x);
copy(b, b + r - l + 1, a + l);
return cnt;
}
int main() {
int n;
while (scanf("%d", &n), n) {
for (int i = 1; i <= n; i++) read(a[i]);
printf("%I64d\n", Merge(a, 1, n));
}
return 0;
}(3)、并查集的基础应用
第一次做并查集的题,粗略学了下并查集。
这里分类上面没给题,自己找了几个题做做。
1、poj 1611
题意:有一个学校,有N个学生,编号为0-N-1,现在0号学生感染了非典,凡是和0在一个社团的人就会感染,并且这些人如果还参加了别的社团,他所在的社团照样全部感染,求感染的人数。
分析:简单并查集,求包含0号学生的联通分支的个数,在路径压缩的同时统计人数,最后输出0号学生所在根节点的数目即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 30010;
int p[N], r[N], num[N];
void init(int n) {
for (int i = 0; i <= n; i++) p[i] = i, num[i] = 1;
}
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
void unite(int x, int y) {
x = find(x); y = find(y);
if (x == y) return ;
num[x] += num[y];
p[y] = x;
}
int main() {
int n, m;
while (scanf("%d %d", &n, &m), n) {
init(n);
for (int i = 0; i < m; i++) {
int k, f;
scanf("%d %d", &k, &f);
for (int j = 1; j < k; j++) {
int a;
scanf("%d", &a);
unite(f, a);
}
}
printf("%d\n", num[find(p[0])]);
}
return 0;
}2、poj 1182
题意:中文题,不多说了。
分析:网上的题解一大堆。。。不过还是觉得挑战上面的解法清晰易懂。输入的动物关系比较多,要快速判断矛盾的话,需要维护已得的关系,那么可以使用并查集,把会同时发生或不发生的情况合并至一组内,为每个动物建立3个并查集的元素,i属于A,i属于B,i属于C,然后对于每一条信息,首先判断是否会产生矛盾,不矛盾则更新所有可能的关系。
以x,x+n,x+2*n代表x-A,x-B,x-C。
对于x和y是同类,我们只需要判断x是不是吃y,即判断x属于A和y属于B是否在一组内,还有y是不是吃x,判断x属于A和y属于C是不是在一组内。
x吃y同理判断。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N = 150010;
int r[N], p[N];
void init(int n) {
for (int i = 0; i <= 3*n; i++) r[i] = 0, p[i] = i;
}
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
void unite(int x, int y) {
x = find(x), y = find(y);
if (r[x] < r[y]) p[x] = y;
else {
p[y] = x;
if (r[x] == r[y]) r[x]++;
}
}
bool same(int x, int y) { return find(x) == find(y); }
int main() {
int n, k, ans = 0;
scanf("%d %d", &n, &k);
init(n+1);
for (int i = 0; i < k; i++) {
int d, x, y;
scanf("%d %d %d", &d, &x, &y);
if (x < 0 || x > n || y < 0 || y > n) { ans++; continue; }
if (d == 1) {
if (same(x, y + n) || same(x, y + 2*n)) ans++;
else {
unite(x, y);
unite(x + n, y + n);
unite(x + 2*n, y + 2*n);
}
}
else {
if (same(x, y) || same(x, y + 2*n)) ans++;
else {
unite(x, y + n);
unite(x + n, y + 2*n);
unite(x + 2*n, y);
}
}
}
printf("%d\n", ans);
return 0;
}3、hdu 1272
题意:中文题,不多说了。
分析:普通并查集的基础应用,判断回路和判联通。。。题目唯一的坑点就是没有房间也输出Yes。判断回路可以在输入的时候一并处理,判联通就更容易了,判断根节点的数目就可以了。
#include <cstdio>
#include <cstring>
#include <queue>
#include <set>
using namespace std;
const int N = 100010;
int r[N], p[N], vis[N];
void init() {
for (int i = 0; i <= 100010; i++) r[i] = 0, p[i] = i;
memset(vis, 0, sizeof(vis));
}
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
void unite(int x, int y) {
x = find(x), y = find(y);
if (r[x] < r[y]) p[x] = y;
else {
p[y] = x;
if (r[x] == r[y]) r[x]++;
}
}
bool same(int x, int y) { return find(x) == find(y); }
int main() {
int a, b;
while (scanf("%d %d", &a, &b), a != -1) {
if (a == b && a == 0) { puts("Yes"); continue; }
init();
vis[a] = vis[b] = 1;
p[find(a)] = find(b);
int flag = 1, ma = 0, mi = N;
mi = min(mi, a); mi = min(mi, b);
ma = max(ma, a); ma = max(ma, b);
while (scanf("%d %d", &a, &b), a) {
vis[a] = vis[b] = 1;
mi = min(mi, a); mi = min(mi, b);
ma = max(ma, a); ma = max(ma, b);
if (!flag) continue;
int fa = find(a), fb = find(b);
if (fa == fb) flag = 0;
if (flag) p[fa] = fb;
}
if (flag) {
int cnt = 0;
for (int i = mi; i <= ma && flag; i++) {
if (vis[i]) {
if (p[i] == i) cnt++;
if (cnt > 1) flag = 0;
}
}
}
puts(flag ? "Yes" : "No");
}
return 0;
}(4)、哈希表和二分查找等高效查找法
说实话,这个部分的几个题比较坑爹。。。不过这也再次证明了我的蒟蒻本质。
1、poj 3349
题意:给定n片雪花,每个雪花有六个值代表他们的花瓣长度,长度由逆时针方向或者顺时针方向给出,起点可以任何一处开始。判断是否有至少一对相同的雪花。
分析:明显需要实现快速查找,很容易想到通过他们的和来建立映射,刚开始做的时候一直在折腾,因为总觉得手写数据结构是件麻烦事,但是患上STL依赖症毕竟不是什么好事。不过题目给了4s,我反复跑数据,其实用map实现效率也不是很低,本地跑100000片雪花只要0.8s左右,而且生成的数据是不存在相同雪花的,如果有相同的雪花,那么后面的不需要查找了,这样时间会更短。但是交上去就是超时,没办法。。。
所以说这题只能手动实现hash表,做完之后在discuss里面看了看是否有大牛贴了高效解法,有人说直接排序可以过。。。但是本人认为直接排序破坏了雪花长度的相对位置,那么即使六个花瓣的长度相同,也不一定是相同的,这个很容易知道,我也亲自实验了下,居然过了,表示很无语。。。。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <numeric>
using namespace std;
const int maxsize = 59997;
const int N = 100005;
struct snow{
int len[7];
int next;
}s[N];
int head[maxsize];
template <class T>
inline void read(T &res) {
char c; res = 0;
while (!isdigit(c = getchar()));
while (isdigit(c)) res = res * 10 + c - '0', c = getchar();
}
void init() {
memset(head, -1, sizeof(head));
}
bool cal(int i, int j) {
for (int t = 0; t < 6; t++) {
if (s[i].len[t] == s[j].len[0]) {
int flag = 1;
for (int k = 1, l = t+1; flag && k < 6; l++, k++) {
if (l == 6) l = 0;
if (s[i].len[l] != s[j].len[k]) flag = 0;
}
if (flag) return 1;
flag = 1;
for (int k = 1, l = t-1; flag && k < 6; l--, k++) {
if (l == -1) l = 5;
if (s[i].len[l] != s[j].len[k]) flag = 0;
}
if (flag) return 1;
}
}
return 0;
}
bool insert(int sum, int i) {
int mod = sum % maxsize, flag = 1;
for (int j = head[mod]; j != -1 && flag; j = s[j].next)
if (cal(i, j)) flag = 0;
if (flag) {
s[i].next = head[mod];
head[mod] = i;
return 0;
}
return 1;
}
int main() {
int n, flag = 1;
read(n); init();
for (int i = 0; i < n; i++) {
for (int j = 0; j < 6; j++) read(s[i].len[j]);
if (flag) {
if (insert(accumulate(s[i].len, s[i].len + 6, 0), i)) flag = 0;
}
}
puts(flag ? "No two snowflakes are alike." : "Twin snowflakes found.");
return 0;
}2、poj3274
题意:给n个二进制形式为k位的十进制数,第i个二进制位代表第i个特征是否存在,求最大的一个区间,使得里面的数对于所有的二进制位,该位为1的个数都相同。
分析:被这题坑到要吐了,强行做了一天,没做出来。首先,我们需要快速计算一个区间内第i个位为1的个数,那么我们可以使用预处理出区间个数,后面可以O(1)计算了。思来想去,觉得可以枚举每个二进制位的个数,这样一定可以得出解,然后对于每个个数x,选定第一个数,枚举区间的起点,然后我们需要快速计算每个二进制位从该起点恰好包含x个时的区间长度,这样的话取所有二进制位的最小值即是以当前起点为区间的符合条件的区间长度了。怎么快速计算呢?想到了hash,对于每个二进制位,预处理出这个二进制位为1时的位置,这样就可以O(1)查找了。所以复杂度为O(k*k*n),那么最多也就30*30*100000 = 90000000的计算量,加上之前O(n)的预处理,2s之内得出解应该没问题,调试许久终于过了样例,交上去超时。。。。。。那时候心里面那个悲伤真是无法言语。。。其实想到这个办法已然不容易了,这是我思来想去用尽各种办法才最后确定的。。。我想应该是常数太大导致O(k*k*n)的复杂度也超时了。
没办法,找了份题解看了下,真是给跪了。。。好吧,就不吐槽了。
做之前需要对题目进行转化,那个转化确实很精妙,这里借用下,感谢该博主。。
以数组sum[i][j]表示从第1到第i头cow属性j的出现次数。所以题目要求等价为:
求满足
sum[i][0]-sum[j][0]=sum[i][1]-sum[j][1]=.....=sum[i][k-1]-sum[j][k-1] (j<i)
中最大的i-j
将上式变换可得到
sum[i][1]-sum[i][0] = sum[j][1]-sum[j][0]
sum[i][2]-sum[i][0] = sum[j][2]-sum[j][0]
......
sum[i][k-1]-sum[i][0] = sum[j][k-1]-sum[j][0]
令C[i][y]=sum[i][y]-sum[i][0] (0<y<k)
初始条件C[0][0~k-1]=0
所以只需求满足C[i][]==C[j][] 中最大的i-j,其中0<=j<i<=n。
C[i][]==C[j][] 即二维数组C[][]第i行与第j行对应列的值相等,
那么原题就转化为求C数组中 相等且相隔最远的两行的距离i-j。
知道了上面的结论,和上题一样了,直接查找,为提高效率,使用hash实现。不过题目有一个坑点就是,必须首先对0进行映射,因为c[0]都为0,如果不处理,那么数据3 1 1 1 1就会得到错误答案2,而不是3。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cctype>
#include <numeric>
using namespace std;
const int N = 100005;
const int maxsize = 39997;
int num[N][35];
int c[N][35], next[N];
int head[maxsize];
template <class T>
inline void read(T &res) {
char c; res = 0;
while (!isdigit(c = getchar()));
while (isdigit(c)) res = res * 10 + c - '0', c = getchar();
}
int n, k, ans;
bool cal(int i, int j) {
for (int x = 1; x < k; x++)
if (c[i][x] != c[j][x]) return 0;
return 1;
}
void init() {
memset(head, -1, sizeof(head));
}
void insert(int i) {
int sum = accumulate(c[i]+1, c[i]+k, 0);
sum < 0 ? sum += maxsize : sum %= maxsize;
for (int j = head[sum]; j != -1; j = next[j])
if (cal(i, j)) ans = max(ans, i - j);
next[i] = head[sum], head[sum] = i;
}
int main() {
read(n); read(k); init();
next[0] = -1; head[0] = 0;
for (int i = 1; i <= n; i++) {
int val, j = 0;
read(val);
while (val) {
num[i][j] = num[i-1][j] + (val & 1);
c[i][j] = num[i][j] - num[i][0];
val >>= 1; j++;
}
for (; j < k; j++) num[i][j] = num[i-1][j], c[i][j] = num[i][j] - num[i][0];
insert(i);
}
printf("%d\n", ans);
return 0;
}从这题可以看出来,有时直接处理比较麻烦,找不到比较好的关键点,但是如果把题目的要求进行转化,就可以很容易的得出高效或者是简单的算法,而且这种转化通常都会在包含公式或者式子的地方。这也体现了数学中转化思维的美,当我们首先把一个题目的要求用某些变量或者计算式表示的时候,直接处理反而不容易实现,需要进行转化。解题需要有这样的意识。
3、poj2151
题意:在比赛中,共M道题,T个队,pij表示第i队解出第j题的概率。求每队至少解出一题且冠军队至少解出N道题的概率。
分析:明显的概率计算,不知道为什么被归类到查找中来了,可能那位分类的大牛输错了吧。。
这题就当是我概率dp的入门好了。
概率问题一般可以从反面考虑,这题完全可以细细分类,那么再把每类用乘法原理计算得出,最后再用加法原理求出答案。但是这如果要分出每种可能的情况,那么几乎是不可能的,因为可能种类数太多。我们必须找到关键点。
直接计算不方便,我们从反面考虑,首先根据题意我们可以计算出每个队伍至少都过一题的概率p1,然后要计算出每个队伍至少过一题且有一个队伍至少过n题的概率,那么直接减去每个队伍过题数都在1到n-1题的这种情况就可以了。因为在每个队伍至少过一题中的整体中,每个队伍过的题数在1到n-1题中的反面就是至少有一个队伍过了n题以上。这样,关键是计算每个队伍过的题数都在1到n-1的概率。我们可以进行动态规划,不好直接对整体进行规划,每个队伍单独考虑,令dp[i][j][k]为第i个队伍在前j题过k题的概率,这样dp[i][j][k] = dp[i][j-1][k-1] * (1-p[i][j]) + dp[i][j-1][k] * p[i][j]。做0题的概率单独考虑就可以了,这样,我们就可以知道了每个队伍过一定题目数的概率了。
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1010;
const int M = 40;
double p[N][M], dpt[N][M][M], dpa[N][M];
int m, t, n;
void init() {
for (int i = 1; i <= t; i++)
for (int j = 1; j <= m; j++) memset(dpt[i][j], 0, 4 * (m+5));
for (int i = 1; i <= t; i++) memset(dpa[i], 0, 4 * (m+5));
for (int i = 1; i <= t; i++) dpt[i][0][0] = 1.0;
}
int main() {
while (scanf("%d %d %d", &m, &t, &n), m) {
double p1 = 1, p2 = 1;
for (int i = 1; i <= t; i++) {
double tmp = 1;
for (int j = 1; j <= m; j++) scanf("%lf", &p[i][j]), tmp *= 1 - p[i][j];
p1 *= 1 - tmp;
}
init();
for (int i = 1; i <= t; i++) {
for (int j = 1; j <= m; j++) {
dpt[i][j][0] = dpt[i][j-1][0] * (1 - p[i][j]);
for (int k = 1; k <= j; k++) {
dpt[i][j][k] = dpt[i][j-1][k-1] * p[i][j] + dpt[i][j-1][k] * (1 - p[i][j]);
}
}
for (int k = 1; k <= n-1; k++) dpa[i][n-1] += dpt[i][m][k];
}
for (int i = 1; i <= t; i++) p2 *= dpa[i][n-1];
printf("%.3f\n", p1 - p2);
}
return 0;
}4、poj1840
题意:给出一个5元3次方程,输入其5个系数,求它的解的个数。其中系数 ai∈[-50,50] 自变量xi∈[-50,0)∪(0,50]
分析:不能直接暴力,题目可以等价为一个查找问题,对于固定的x1和x2,查找有多少组x3、x4、x5使得满足题意。这样我们可以通过打表,然后实现快速查找,二分,hash都行,不过hash的话注意结果的范围比较大,开数组不能太大,而且要对负数进行处理,使得下标变为正数。不过值得一提的是这题仅仅把x1、x2打表使用map也超时。。。map的效率有这么低吗?10000个数也无法承受?不过hash数组的时候只能用short,否则会爆内存。
不过这题完全可以离散化,开hash数组浪费空间是巨大的。
hash:
#include <cstdio>
using namespace std;
const int N = 25000000;
short hash[N];
int main() {
int a1, a2, a3, a4, a5, l = 0;
scanf("%d %d %d %d %d", &a1, &a2, &a3, &a4, &a5);
for (int i = -50; i <= 50; i++) {
if (!i) continue;
for (int j = -50; j <= 50; j++) {
if (!j) continue;
int sum = a1*i*i*i + a2*j*j*j;
if (sum < 0) sum += N;
hash[sum]++;
}
}
int ans = 0;
for (int i = -50; i <= 50; i++) {
if (!i) continue;
for (int j = -50; j <= 50; j++) {
if (!j) continue;
for (int k = -50; k <= 50; k++) {
if (!k) continue;
int sum = a3*i*i*i + a4*j*j*j + a5*k*k*k;
sum = -sum;
if (sum < 0) sum += N;
ans += hash[sum];
}
}
}
printf("%d\n", ans);
return 0;
}
二分:
#include <cstdio>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <cctype>
#include <algorithm>
using namespace std;
const int N = 1000010;
typedef long long LL;
LL val[N];
int main() {
int a1, a2, a3, a4, a5, l = 0;
scanf("%d %d %d %d %d", &a1, &a2, &a3, &a4, &a5);
for (int i = -50; i <= 50; i++) {
if (!i) continue;
for (int j = -50; j <= 50; j++) {
if (!j) continue;
for (int k = -50; k <= 50; k++) {
if (!k) continue;
val[l++] = a3*i*i*i + a4*j*j*j + a5*k*k*k;
}
}
}
sort(val, val+l);
LL ans = 0;
for (int i = -50; i <= 50; i++) {
if (!i) continue;
for (int j = -50; j <= 50; j++) {
if (!j) continue;
LL sum = a1*i*i*i + a2*j*j*j;
ans += (upper_bound(val, val+l, -sum) - lower_bound(val, val+l, -sum));
}
}
printf("%I64d\n", ans);
return 0;
}5、poj2002
题意:给定N个点,求出这些点一共可以构成多少个正方形。
分析:刚开始做的时候在那乱搞,什么以边长实现hash等等。。。没办法,最后还是没做出来,后面才知道知道一个正方形的一条边的两个点,居然可以推导出另两个点的坐标。。。真是学新姿势了。。。
已知两个点(x1,y1) (x2,y2)
则:x3=x1+(y1-y2) y3= y1-(x1-x2) x4=x2+(y1-y2) y4= y2-(x1-x2)
或 x3=x1-(y1-y2) y3= y1+(x1-x2) x4=x2-(y1-y2) y4= y2+(x1-x2)
有了这个结论,我们只需要枚举正方形的一条边两个点,然后直接查找另外两个点存不存在了,二分,hash也都行。我们可以给坐标排个序,这样枚举的时候可以只枚举正方形的两条边了,而不需要枚举四条边,最后的结果除以2就可以了。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
#include <map>
using namespace std;
typedef long long LL;
const int N = 1005;
const int maxsize = 17997;
struct po {
int x, y;
bool operator < (const po& a) const { return x == a.x ? y < a.y : x < a.x; }
}p[N];
int next[N], head[maxsize];
void insert(int i) {
int mod = (p[i].x * p[i].x + p[i].y * p[i].y) % maxsize;
next[i] = head[mod], head[mod] = i;
}
bool cal(int x1, int y1, int x2, int y2) {
int m1 = (x1 * x1 + y1 * y1) % maxsize, m2 = (x2 * x2 + y2 * y2) % maxsize;
int flag = 1;
for (int j = head[m1]; j != -1 && flag; j = next[j])
if (p[j].x == x1 && p[j].y == y1) flag = 0;
if (flag) return 0;
for (int j = head[m2]; j != -1; j = next[j])
if (p[j].x == x2 && p[j].y == y2) return 1;
return 0;
}
int main() {
int n;
while (scanf("%d", &n), n) {
for (int i = 0; i < n; i++) scanf("%d %d", &p[i].x, &p[i].y);
if (n < 4) { puts("0"); continue; }
memset(head, -1, sizeof(head));
sort(p, p+n);
for (int i = 0; i < n; i++) insert(i);
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
if (p[i].y < p[j].y) {
int dx = p[i].x - p[j].x, dy = p[i].y - p[j].y;
int x1 = p[i].x + dy, y1 = p[i].y - dx;
int x2 = p[j].x + dy, y2 = p[j].y - dx;
if (cal(x1, y1, x2, y2)) ans++;
x1 = p[i].x - dy, y1 = p[i].y + dx;
x2 = p[j].x - dy, y2 = p[j].y + dx;
if (cal(x1, y1, x2, y2)) ans++;
}
}
}
printf("%d\n", ans >> 1);
}
return 0;
}
二分:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
#include <map>
using namespace std;
typedef long long LL;
const int N = 1005;
struct po {
int x, y;
bool operator < (const po& a) const { return x == a.x ? y < a.y : x < a.x; }
bool operator == (const po& a) const { return x == a.x && y == a.y; }
po(int x = 0, int y = 0) : x(x), y(y) {}
}p[N];
int main() {
int n;
while (scanf("%d", &n), n) {
for (int i = 0; i < n; i++) scanf("%d %d", &p[i].x, &p[i].y);
if (n < 4) { puts("0"); continue; }
sort(p, p+n);
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
if (p[i].y < p[j].y) {
int dx = p[i].x - p[j].x, dy = p[i].y - p[j].y;
int x1 = p[i].x + dy, y1 = p[i].y - dx;
int x2 = p[j].x + dy, y2 = p[j].y - dx;
po *f = lower_bound(p, p+n, po(x1, y1));
if (*f == po(x1, y1)) {
f = lower_bound(p, p+n, po(x2, y2));
if (*f == po(x2, y2)) ans++;
}
x1 = p[i].x - dy, y1 = p[i].y + dx;
x2 = p[j].x - dy, y2 = p[j].y + dx;
f = lower_bound(p, p+n, po(x1, y1));
if (*f == po(x1, y1)) {
f = lower_bound(p, p+n, po(x2, y2));
if (*f == po(x2, y2)) ans++;
}
}
}
}
printf("%d\n", ans >> 1);
}
return 0;
}6、poj2503
题意:给定字符串的一一对应关系,然后对应查找,不存在输出eh。
分析:这题可以直接map水过,时间大概为2100ms,当然字典树也可以。
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <algorithm>
using namespace std;
const int N = 100010;
map<string, int> mp;
char s[N][25], str[12];
int main() {
int i = 1;
while (gets(s[i]), strlen(s[i])) {
int c = strchr(s[i], ' ') - s[i];
s[i][c] = 0;
mp[s[i]+c+1] = i++;
}
while (~scanf("%s", str)) printf("%s\n", mp[str] ? s[mp[str]] : "eh");
return 0;
}(5)哈夫曼树
1、poj3253
题意:有一块长木板,要经过n-1次切割将其切成n块FJ想要的木板,对于每块木板,每切割一次,将会消耗和这条木板长度值相等的金钱,问最少需要多少钱,可将木板切成自己想要的n块。
分析:哈夫曼树的构造策略,采用贪心的思想实现,每次选取长度最小的合成上一次的木块,这样合成的木块长度最短,最后的钱也最少。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long LL;
int main() {
int n;
priority_queue<int, vector<int>, greater<int> > q;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int len;
scanf("%d", &len);
q.push(len);
}
LL ans = 0;
while (q.size() > 1) {
int a = q.top(); q.pop();
int b = q.top(); q.pop();
ans += (a + b);
q.push(a+b);
}
printf("%I64d\n", ans);
return 0;
}(6)堆
1、poj1456
题意:有N件商品,分别给出商品的价值和销售的最后期限,只要在最后日期之前销售处,就能得到相应的利润,并且销售该商品需要1天时间。问销售的最大利润。
分析:贪心思想,策略是要是使得利润大的尽量先销售,且尽量留更多的时间给其他的商品。我们只需要从截止日期往前找,第一个未选过的日期便确定,这样可以保证销售的利润最大化,因为必定不存在比其更优的解。而实现时可以采用堆优化。
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 10010;
struct product{
int p, d;
bool operator < (const product& x) const {
return p == x.p ? d < x.d : p > x.p;
}
}pro[N];
int vis[N];
int main() {
int n;
while (~scanf("%d", &n)) {
if (!n) { puts("0"); continue; }
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++) scanf("%d %d", &pro[i].p, &pro[i].d);
sort(pro, pro+n);
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = pro[i].d; j; j--) {
if (!vis[j]) {
vis[j] = 1, ans += pro[i].p;
break;
}
}
}
printf("%d\n", ans);
}
return 0;
}(7)trie树
1、poj2513
题意:给定一些木棒,木棒两端都涂上颜色,求是否能将木棒首尾相接,连成一条直线,要求不同木棒相接的一边必须是相同颜色的。
分析:显然是无向图的欧拉路径判定,但是怎么把字符串映射到标号呢?map又要查找又要插入,且数据量比较大,必定超时,hash实现麻烦且冲突会很多,这时我们可以使用trie,其插入和查询是O(k)的,k为字符串长度。
首先回顾一些基本的欧拉回路知识:
1、图G的一个回路,若它恰通过G中每条边一次,则称该回路为欧拉(Euler)回路。
如果一个图只是形成一个连通所有节点的链,且每一点只走一次,则成为欧拉道路。
2、具有欧拉回路或欧拉道路的图称为欧拉图(简称E图)。
3、有向图的欧拉回路
一个有向图存在欧拉回路的前提条件是这个图是个连通图,其次要求其每个点的入度等于出度,或者其中有一个点的出度比入度大1,另一个点的入度比出度大一这样就存在一条欧拉回路。
如果其每个点的入度等于出度则从任意一点出发,可以走出一条欧拉回路,如果是第二种情况,则必须从出度大于入度 1 的点出发到入度大于出度 1 的点结束,走出一条欧拉道路。
4、无向图的欧拉回路
跟有向图一样,首先必须连通,其次有两个奇点或者没有奇点。则满足欧拉回路或欧拉道路,有奇点就从任意一个奇点出发可找到一条欧拉道路,否则从任意一点出发都能找出欧拉回路。
这是第一次接触trie,直接拿大白上面的板子抄过的,值得一提的是采用指针实现效率居然比较低,时间是数组版本的三倍,我想是因为new的原因,new运算符效率低下。应该是我写的太搓了吧。。。
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 500005;
struct trie{
int ch[N][26];
int val[N];
int sz;
trie() { sz = 1; memset(ch[0], 0, sizeof(ch[0])); }
void insert(char *s, int v) {
int u = 0, n = strlen(s);
for (int i = 0; i < n; i++) {
int c = s[i] - 'a';
if (!ch[u][c]) {
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = v;
}
int find(char* s) {
int u = 0, n = strlen(s);
for (int i = 0; i < n; i++) {
int c = s[i] - 'a';
if (!ch[u][c]) return 0;
u = ch[u][c];
}
return val[u];
}
};
char f[12], s[12];
trie r;
int d[N], p[N];
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
int main() {
for (int i = 1; i < N; i++) p[i] = i;
int cnt = 0;
while (~scanf("%s %s", f, s)) {
int u = r.find(f);
if (!u) r.insert(f, u = ++cnt);
int v = r.find(s);
if (!v) r.insert(s, v = ++cnt);
d[u]++; d[v]++;
u = find(u), v = find(v);
if (u != v) p[u] = v;
}
if (!strlen(f) && !strlen(s)) puts("Possible");
else {
int flag = 1, cntf = 0, cnts = 0;
for (int i = 1; i <= cnt && flag; i++) {
if (p[i] == i) {
if (++cntf > 1) flag = 0;
}
if (d[i] & 1) {
if (++cnts > 2) flag = 0;
}
}
puts(flag ? "Possible" : "Impossible");
}
return 0;
}
指针实现:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 500005;
int k;
struct trie{
int id;
struct trie* next[26];
trie(int k = 0) {
id = k;
for (int i = 0; i < 26; i++) next[i] = 0;
}
}t[N];
trie* rt;
int cal(char* s) {
trie* r = rt;
int len = strlen(s);
for (int i = 0; i < len; i++) {
int c = s[i] - 'a';
if (!r->next[c]) return 0;
r = r->next[c];
}
return r->id;
}
void insert(char* s, int v) {
trie* r = rt;
for (int i = 0; s[i]; i++) {
int c = s[i] - 'a';
if (!r->next[c]) r->next[c] = new trie(k++);
r = r->next[c];
}
r->id = v;
}
char f[12], s[12];
int d[N], p[N];
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
int main() {
for (int i = 1; i < N; i++) p[i] = i;
int cnt = 0;
rt = new trie();
while (~scanf("%s %s", f, s)) {
int u = cal(f);
if (!u) insert(f, u = ++cnt);
int v = cal(s);
if (!v) insert(s, v = ++cnt);
d[u]++; d[v]++;
u = find(u), v = find(v);
if (u != v) p[u] = v;
}
if (!strlen(f) && !strlen(s)) puts("Possible");
else {
int flag = 1, cntf = 0, cnts = 0;
for (int i = 1; i <= cnt && flag; i++) {
if (p[i] == i) {
if (++cntf > 1) flag = 0;
}
if (d[i] & 1) {
if (++cnts > 2) flag = 0;
}
}
if (cnts & 1) flag = 0;
puts(flag ? "Possible" : "Impossible");
}
return 0;
}(8)总结
这个专题是数据结构部分,数据结构在acm的中的重要性不言而喻,要想熟练掌握绝非易事。这个专题可以帮我们巩固最基本的数据结构,并查集、hash、堆、trie树等用处都很大,不过我觉得这里还可以加上stl,因为很多情况下我们可以直接运用stl,省去了手写各种数据结构的麻烦,stl本身简单实用,只是数据量比较大时往往成为性能瓶颈。数据结构完全可以深入学习,因为往往算法效率第一,而这离不开各种数据结构的优化。而且很多时候数据结构还可以很简单地实现很多功能,比如说判断联通时利用并查集可以很简单的实现,而且还可以求联通分支个数等等。
1、串:串是一种最基本的数据类型,像寻找公共子串、判断一个串是不是另外一个串的子串等都是很常见的,C语言有各种字符串函数,大多是str族的,比如说strlen、strcpy、strcmp、strchr、strstr、strtok、strtol、strrev等等,前面的几个比较常用,后面的虽然不怎么用,但是很方便,省去了手写的麻烦。而且stl有模板字符串类型string,封装了各种操作,但是效率低下,但最主要的一点是很方便。。。。
2、排序:至于排序就不多说了,应用太多太多了。。而且大部分情况下sort一般能应付,支持自己定义比较方式。但是应用排序也要有针对性,nth_element,partial_sort,stable_sort等等。。
3、并查集:并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。集就是让每个元素构成一个单元素的集合,并就是按一定顺序将属于同一组的元素所在的集合合并。
并查集大体分为三个:普通的并查集,带种类的并查集,扩展的并查集(主要是必须指定合并时的父子关系,或者统计一些数据,比如此集合内的元素数目。)
普通并查集的应用有:判环、判连通(求联通分支个数)。
4、查找:通常的查找方式都是枚举,但是枚举离不开低效的阴影,需要有更高效的查找方式,hash就是映射,不过需要处理冲突,如果冲突较少,那么可以达到O(1),具体的实现的话,一般都是除留余数法,处理冲突的方式一般都是建立链表。如果冲突较多,那么查找会达到O(n)的复杂度。至于二分查找,复杂度为O(logn),需要是有序序列。
5、哈夫曼树:
应用不是很多,了解下就好了。
所谓哈夫曼树是指一种带权路径长度最短的二叉树,也称为最优二叉树。
哈夫曼树的构造算法:
假设有n个权值,则构造出得哈夫曼树有n个叶子结点。n个权值分别设为w1,w2,...,wn,则哈夫曼树的构造规则为:
(1)将w1,w2,...,wn看成是有n棵树的森林(每棵树仅有一个结点);
(2)在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
6、堆:
堆的应用主要体现在优先队列上,而优先队列是最常用的容器,经常用来贪心和加速搜索。
7、trie树:
Trie树,又称单词查找树、字典树,是一种树形结构,是一种哈希树的变种,是一种用于快速检索的多叉树结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
字典树(Trie)可以保存一些字符串->值的对应关系。
1万+

被折叠的 条评论
为什么被折叠?



