您的位置:首页 > 其它

sduacm2016级暑假集训 搜索&并查集

2017-07-15 13:56 288 查看
比赛地址

密码 :acm2016

A - 食物链 (POJ1182)

题目链接

【题意】

共有三类动物A、B、C,构成一种食物链关系,A吃B, B吃C,C吃A。

然后有两种说法,描述N个动物的关系。

第一种说法是”1 X Y”,表示X和Y是同类。

第二种说法是”2 X Y”,表示X吃Y。

共给出K句话,问假话的数目。

1) 当前的话与前面的某些真的话冲突,就是假话;

2) 当前的话中X或Y比N大,就是假话;

3) 当前的话表示X吃X,就是假话。

【分析】

对上面的2、3可以直接判断真假,主要是对1的处理。

对每种动物创建3个元素, i-A, i-B, i-C, 分别表示动物i属于A, 动物i属于B, 动物i属于C, 并用这3*N个元素来构造并查集.

并查集里面的每一个组表示组内的所有情况都同时发生或不发生.

对于每一条信息, 按照如下步骤操作:

D=1: 如果判断”x和y属于同一类”不会产生矛盾,则合并元素 x-A 和 y-A, x-B 和 y-B, x-C 和 y-C;

D=2: 如果判断”x吃y”不矛盾, 则合并元素 x-A 和 y-B , x-B 和 y-C, x-C 和 y-A;

其中 判断x和y属于同一类是否会产生矛盾, 即判断”x-A, y-B为同一组, 或者 x-A, y-C同一组为同一组” 是否成立. 如果成立则说明之前已经确定过x,y动物之间的吃与被吃的关系,该句话与先前确定的状态产生了矛盾,这句话为假.

判断x吃y是否会产生矛盾, 即判断”x-A, y-A为同一组, 或者x-A, y-C为同一组”是否成立. 即判断x, y是否已经时同类, 或者已经时y吃x的关系. 如果是上述其中一种, 则该句话与之前已经产生的状态矛盾, 该句话为假.

判断x,y同类, 我们只需要判断x-A, y-A是否为同一组, 而不需要再判断x-B, y-B是否为同一组, 或者x-C, y-C是否为同一组, 因为这是冗余的判断,没有必要. 我们在合并元素的时候,已经把这样表示同样状态的3种状态都设置过了, 判断其中一种就可以了.

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 200000 + 10;

int f[MAX_N];

int findx(int x)
{
if (x==f[x]) return x;
return f[x] = findx(f[x]);
}

void unite(int x,int y)
{
f[findx(x)] = findx(f[y]);
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i=1;i<=3 * n;i++) {
f[i] = i;
}

int ans = 0;
int d, x, y;
while (k--){
scanf("%d%d%d", &d, &x, &y);
if ((x > n)||(y > n)||(d == 2 && x == y)){
ans++;
continue;
}
if (d == 1){
if (findx(x) == findx(y + n) || findx(x) == findx(y + 2 * n)){
ans++;
continue;
}
unite(x, y);
unite(x + n, y + n);
unite(x + 2 * n, y + 2 * n);
}else if (d == 2){
if (findx(x) == findx(y) || findx(x) == findx(y + 2 * n)){
ans++;
continue;
}
unite(x, y + n);
unite(x + n, y + 2 * n);
unite(x + 2 * n, y);
}
}
printf("%d\n", ans);
return 0;
}


B - Wireless Network (POJ2236)

题目链接

【题意】

给出N台电脑及其所在位置坐标,这N台电脑都是已损坏的,需要修复才能使用。

如果两台电脑距离小于等于给定数d,那么它们两个可以通信。如果两太电脑A,B不能直接通信,但是它们都可以与电脑C通信,那么可以认为电脑A,B可以互相通信。

需要在修复时进行测试两台电脑是否可以相互通信。

【分析】

用并查集来维护通信关系,能互相通信的电脑在一个集合里面,不能通信的在另外的集合。

每次修复电脑记录该电脑可用,并去O(n)的维护并查集的关系,测试是否可以通信直接判断是否在一个集合就好了。

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 2000 + 10;

int f[MAX_N];
double x[MAX_N],y[MAX_N];
bool v[MAX_N];

4000
int findx(int x)
{
if (x==f[x]) return x;
return f[x] = findx(f[x]);
}

void unite(int x,int y)
{
f[findx(x)] = findx(f[y]);
}

double dist(int i, int j)
{
return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}

int main()
{
int n, k;
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) f[i] = i;
memset(v,0,sizeof(v));
for (int i=1;i<=n;i++){
scanf("%lf%lf",&x[i],&y[i]);
}
char ch;
int a, b;
while (~scanf("%c",&ch)){
if (ch=='O'){
scanf("%d",&a);
v[a] = true;
for (int i=1;i<=n;i++)
if (i!=a&&v[i]&&dist(a,i)<=k)
unite(a,i);
}else if (ch == 'S'){
scanf("%d%d",&a,&b);
bool flag = false;
if (findx(a)==findx(b)) flag = true;
printf("%s\n",flag ? "SUCCESS":"FAIL");
}
}
return 0;
}


C - The Door Problem (Codeforces 776D)

题目链接

【题意】

有N扇门,M把钥匙,每把钥匙可以控制若干个门的开关,每扇门只能由两把钥匙控制。钥匙可以将原来关着的门打开,原来开着的门关闭,问是否有一种方案可以使得所有的门都关闭。

【分析】

重点在于每扇门只能由两把钥匙控制,那么可以用类似于A题的思路来做这个题。

对于每扇门找出其两把钥匙,

如果该门是关闭的,那么这两把钥匙在同一个集合内,要么都用,要么都不用。

如果该门是开着的,那么这两把钥匙在不同的集合内,只能用其中的一把。

然后用并查集去维护这个关系。i表示用这把钥匙,i+m表示不用这把钥匙(i<=m)

最后判断是否i和i+m在同一个集合。在就输出NO,不在输出YES。

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 200000 + 10;

int f[MAX_N],p[MAX_N];
vector<int> a[MAX_N];

int findx(int x)
{
if (x==f[x]) return x;
return f[x] = findx(f[x]);
}

void unite(int x,int y)
{
f[findx(x)] = findx(f[y]);
}

int main()
{
int n, m, k, x;
scanf("%d%d",&n,&m);
for (int i=1;i<=2*m;i++) f[i] = i;
for (int i=1;i<=n;i++) scanf("%d",&p[i]);
for (int i=1;i<=m;i++){
scanf("%d",&k);
while(k--){
scanf("%d",&x);
a[x].push_back(i);
}
}
for (int i=1;i<=n;i++)
{
int x = a[i][0], y = a[i][1];
if (p[i]==1){
unite(x,y);
unite(x+m,y+m);
}else {
unite(x,y+m);
unite(x+m,y);
}
}
bool flag = false;
for (int i=1;i<=m;i++)
if (findx(i)==findx(i+m)){
flag = true;
break;
}
printf("%s\n",flag ? "NO":"YES");
return 0;
}


D - 小希的迷宫 (HDU1272)

题目链接

【题意】

给出一系列边的连接关系,问最后是否构成一棵树。

【分析】

显然只需要判断树的几个性质就好

边数m=点数n-1,并且不构成回路(自环、多重边)

注意0 0 的情况需要特判输出YES

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 200000 + 10;

int f[MAX_N];
bool v[MAX_N];

int findx(int x)
{
if (x==f[x]) return x;
return f[x] = findx(f[x]);
}

void unite(int x,int y)
{
f[findx(x)] = findx(f[y]);
}

int main()
{
int a, b;
while(~scanf("%d%d",&a,&b)&&(a!=-1)){
if (a==0&&b==0){
printf("Yes\n");
continue;
}
memset(v,0,sizeof(v));
for (int i=0;i<MAX_N;i++) f[i] = i;
bool flag = false;
int cnt = 0, tot = 0;
unite(a,b);
cnt++;
if (!v[a]) {tot++;v[a] = true;}
if (!v[b]) {tot++;v[b] = true;}
while(scanf("%d%d",&a,&b)&&a){
cnt++;
if (!v[a]) {
tot++;
v[a] = true;
}
if (!v[b]) {
tot++;
v[b] = true;
}
if (findx(a)!=findx(b)){
unite(a,b);
}else {
flag = true;
}
//printf("%d %d   %d %d\n",a,b,findx(a),findx(b));
}
if (cnt!=tot-1) flag = true;
printf("%s\n",flag ? "No":"Yes");
}
return 0;
}


E - Island Puzzle (Codeforces 634A)

题目链接

【题意】

题目大意是有N座岛,我们可以假设这些岛是横向顺次连接的,并且最后一个岛和第一个岛也是相连的,现在这个数列中有一个0,它的作用就是0可以和相邻的两个位置上的数字互换位置从而到达和下面数列相同的状态。

【分析】

分析数据我们会发现,0不需要加入到数组中,因为不管怎么移动,除1之外其他的数都会是一个顺序,只不过开始的坐标不同,for example 1230,可以变成0231,所以我们只需要找到第二个数组中第一个数组的第一个数的下标,在第二数组遍历一遍判断是否符合条件就好。

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 200000 + 10;
const int MAX_M = 400000 + 10;

int a[MAX_N], b[MAX_N];

int main()
{
int n,x,cnt = 0;
scanf("%d",&n);
for (int i=0;i<n;i++){
scanf("%d",&x);
if (x != 0) a[cnt++]=x;
}
int pos;
cnt = 0;
for (int i=0;i<n;i++){
scanf("%d",&x);
if (x == 0) continue;
b[cnt++] = x;
if (x==a[0]) pos = cnt - 1;
}
bool flag = false;
for (int i=0;i<n-1;i++)
if (a[i]!=b[(i+pos)%(n-1)]){
flag = true;
break;
}
printf("%s\n",flag ? "NO":"YES");
}


F - The Tag Game (Codeforces 813C)

题目链接

【题意】

给出一棵树,上面由两个可以移动的人,一个人在根节点1,另一个人在x点(x≠1),他们都可以移动,求两人相见的总步数之和(在原地不动也算一步)。

其中在根节点的人想要早一点相遇,在x的点的人想晚一点相遇。

【分析】

分别从1和x跑一边dfs,求出1和x到其他点的距离,然后枚举这些点到1和x的距离,

如果该点到点1的距离大于到点x的距离,那么就代表x可以到这个点再往其他地方走,记录最大的距离为ans,否则1和x就会相遇。

注意最后答案是ans*2。

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 200000 + 10;

vector<int> v[MAX_N];
int dist1[MAX_N], dist2[MAX_N];
void dfs(int now, int pre, int step, int *dis)
{
dis[now] = step;
for (int i=0;i<v[now].size();i++){
int next = v[now][i];
if (next == pre) continue;
dfs(next,now,step+1,dis);
}
}

int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i=0;i<=n;i++) v[i].clear();
int a, b;
for (int i=1;i<n;i++){
scanf("%d%d",&a,&b);
v[a].push_back(b);
v[b].push_back(a);
}
dfs(1,-1,0,dist1);
dfs(k,-1,0,dist2);
int ans = 0;
for (int i=2;i<=n;i++){
if (dist1[i]>dist2[i])
ans = max(ans,dist1[i]);
}
printf("%d\n", ans * 2);
return 0;
}


G - Mike and Shortcuts (Codeforces 689B)

题目链接

【题意】

给出n个点,每个点到相邻点的距离为1可以双向到达,然后加上若干条i到ai的距离为1的单向边,从点1出发,求到所有点的最短路。

【分析】

按题意建图,跑一遍BFS(所有边权为1)或其他可行的最短路算法就可以了。

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 200000 + 10;
const int MAX_M = 400000 + 10;

int d[MAX_N],head[MAX_N];
int cnt,n,m;
bool v[MAX_N];
struct node{
int from,to,next;
} p[MAX_M * 4];

void add(int u,int v)
{
p[cnt].from= u;
p[cnt].to= v;
p[cnt].next= head[u];
head[u]= cnt++;
}
void spfa(int s)
{
memset(d,INF,sizeof(d));
memset(v,false,sizeof(v));
queue<int> q;
d[s]=0;v[s]=true;
q.push(s);
while (!q.empty()){
int u=q.front();
q.pop();
v[u]=true;
for (int i=head[u];i!=-1;i=p[i].next){
int t=p[i].to;
if (d[t]>d[u]+1){
d[t]=d[u]+1;
if (!v[t]){
v[t]=true;
q.push(t);
}
}
}
}
}

int main()
{
cnt=0;
memset(head, -1, sizeof(head));
scanf("%d",&n);
for (int i=2;i<=n;i++){
//printf("%d %d\n",i-1,i);
add(i-1,i);
add(i,i-1);
}
int x;
for (int i=1;i<=n;i++){
scanf("%d",&x);
if (i!=x) add(i,x);
}
spfa(1);
for (int i=1;i<n;i++) printf("%d ",d[i]);
printf("%d\n",d
);
return 0;
}


H - A strange lift (HDU1548)

题目链接

【题意】

给出N层高的楼,并给出每层楼可以上下的层数Ki,只能上下Ki层,并且1<=i+Ki<=N,问到这个地方的最短次数,如果到不了输出-1

【分析】

虽然多组数据,但是N是非常小的,DFS、BFS都是能过的,感觉DFS更好打但很尴尬一开始DFS忘了回溯qwq,然后没看出来就改BFS了,一遍就过了。

这个题的搜索状态还是很好找的,不多解释了。

【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const int MAX_N = 2000 + 10;

bool v[MAX_N];
int p[MAX_N];
int ans, a, b, n;
struct node{
int num,step;
};
void bfs()
{
memset(v,false,sizeof(v));
queue<node> q;
q.push((node){a,0});
v[a] = true;
while (!q.empty())
{
node x = q.front();
q.pop();
if (x.num == b){
ans = x.step;
return ;
}
int y = x.num + p[x.num];
if (y>=1&&y<=n&&!v[y]){
q.push((node){y,x.step+1});
v[y] = true;
}
y = x.num - p[x.num];
if (y>=1&&y<=n&&!v[y]){
q.push((node){y,x.step+1});
v[y] = true;
}
}
}

int main()
{
while (~scanf("%d",&n)&&n){
//memset(v,false,sizeof(v));
scanf("%d%d",&a,&b);
for (int i=1;i<=n;i++) scanf("%d",&p[i]);
//v[a] = true;
ans = INF;
bfs();
printf("%d\n",ans==INF?-1:ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息