之前只知道简单并查集,做了才学会了带权并查集。感觉很开心。。
D - How Many Answers Are Wrong
带权并查集的模板题,不过这题也有需要注意的地方。
1,sum[A]的意义是从A到rootA(rootA<=A)的和,关键点是区间前开后闭(可以想一想)。
2,由上一点也可以得出,如果想求从A到B的sum,那么需要把A–。
代码如下:
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn = 2e5 + 5;
int f[maxn], sum[maxn];
int find(int a)
{
int pre = f[a];
if (a != f[a])
{
f[a] = find(f[a]);
sum[a] += sum[pre];
}
return f[a];
}
int main()
{
int N, M;
while (~scanf("%d %d",&N,&M))
{
int A, B, S;
int ans = 0;
for (int i = 0;i <= N;i++)f[i] = i, sum[i] = 0;
for (int i = 1;i <= M;i++)
{
scanf("%d %d %d", &A, &B, &S);
A--;
int x = find(A), y = find(B);
if (x != y)
{
f[y] = x;
sum[y] = sum[A] + S - sum[B];
}
else
{
if (sum[B] - sum[A] != S)
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}
E - 食物链
经典的带权并查集题,据说会做这道题大部分并查集都可以秒了(雾。。)
一开始理解错题意了,没明白题目这句话,A吃B,B吃C,C吃A。每个动物都是A,B,C中的一种。
所以实际上动物的被吃关系就组成了一个环。
我们现在弄一个结构体.
struct node
{
int parent, relation; //relation =0 similar
//=1 parent eat this
//=2 this eat parent.
//so it like vector x->rootx
node(int _p,int _r):parent(_p),relation(_r){}
node(){}
}nodes[maxn];
relation代表当前这个动物和他祖先的关系(带权并查集的relation大多数都是描述当前点和他祖先的关系,只要找到这个关系的数学表达式就能用带权并查集!)。
当relation=0时,代表是同类。
当relation=1时,代表祖先吃当前动物。
当relation=2时,代表当前动物吃祖先。
现在,我们设向量
AB−→−
A
B
→
代表A和B的关系。
当|
AB−→−
A
B
→
|=0时,代表A和B是同类。
|
AB−→−
A
B
→
|=1时,代表B吃A。
|
AB−→−
A
B
→
|=2时,代表A吃B。
那么容易发现结构体的relation其实相当于|
AROOTA−→−−−−−−
A
R
O
O
T
A
→
|向量。
我们现在来解决find中的路径压缩的relation。
a的roota在路径压缩过程中先变成了roota2,那么现在a的relation就需要指向roota2了。通过向量我们可以很容易得到:
a->roota2=a->roota+roota->roota2.
所以它的值也就更新成如代码中所写的值了。
再看Union。
其实思考的方式仍然靠向量,唯一需要注意的地方是向量的方向不能弄反了!
我们现在先来看输入 d x y。
当d=1时,x和y是同类
当d=2时,x吃y。
所以我们把d减1。那么d的值就是代表y->x的值。
再回到Union中。
rooty的祖先先更新成rootx
那么rooty->rootx=rooty->y+y->x+x->rootx。
判断假话当然也是根据向量,相信读者根据上面的想法也能理解判断的条件,这里就不讲啦。。
代码如下:
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 5e4 + 5;
struct node
{
int parent, relation; //relation =0 similar
//=1 parent eat this
//=2 this eat parent.
//so it like vector x->rootx
node(int _p,int _r):parent(_p),relation(_r){}
node(){}
}nodes[maxn];
int find(int idx)
{
if (nodes[idx].parent != idx)
{
int pre = nodes[idx].parent;
nodes[idx].parent = find(nodes[idx].parent);
nodes[idx].relation = (nodes[idx].relation + nodes[pre].relation) % 3;
}
return nodes[idx].parent;
}
void Union(int x,int y,int rootx, int rooty, int val)
{
nodes[rooty].parent = rootx;
nodes[rooty].relation = (3 - nodes[y].relation + val - 1+nodes[x].relation) % 3;
}
int main()
{
int N, K;
scanf("%d %d", &N, &K);
for (int i = 1;i <= N;i++)
nodes[i].parent = i, nodes[i].relation = 0;
int opt, x, y, rootx, rooty, ans = 0;
for (int i = 1;i <= K;i++)
{
scanf("%d %d %d", &opt, &x, &y);
if (x > N || y > N) { ans++;continue; }
else if (opt == 2 && x == y) { ans++;continue; }
rootx = find(x);
rooty = find(y);
if (rootx != rooty)
{
Union(x, y, rootx, rooty, opt );
}
else
{
if ((nodes[y].relation + 3 - nodes[x].relation) % 3 != opt - 1)
ans++;
}
}
cout << ans << endl;
}
F - True Liars
目前为止第一道,并查集和其他考点一起运用的好题。然而考点一混博主就没思路了。。。老感觉算法搞清楚了,却还是做不出题目来。。
回到题目上来,上一题我们就明白了一个道理,带权并查集的relation实际上就是当前节点与祖先的一个关系表示。那么这题如何建立并查集关系域呢?
聪明的人可以发现,当一个人说别人是好人的话,那么这个人和他说的那个人肯定是一类人,如果说别人是坏人的话,那么这个人和他说的那个人肯定不是一类人。
(精华啊!)。
所以relation=0,代表它和祖先是同类,relation=1,代表它和祖先不是同类。
跑一遍并查集后,就可以把整个大集合分成若干和小集合。每个集合中有好人也坏人(相对而言)。那么之后就可以设dp[i][j] 为前i个集合中好人个数为j,同时可以再开一个数组route[i][j] 来记录路径。设集合个数为num,那么在跑完之后,看dp[num][p1]是不是等于1.如果等于1,就沿着路径往前跑就行了。
代码如下:
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1005;
struct node
{
int father, relation;
node(int _f,int _r):father(_f),relation(_r){}
node(){}
}nodes[maxn];
int find(int idx)
{
if (nodes[idx].father != idx)
{
int pre = nodes[idx].father;
nodes[idx].father = find(pre);
nodes[idx].relation = nodes[idx].relation^nodes[pre].relation;
}
return nodes[idx].father;
}
void Union(int x,int y,int rootx, int rooty,int val)
{
if (rootx < rooty)swap(rootx, rooty);
nodes[rootx].father = rooty;
nodes[rootx].relation = nodes[x].relation^val^nodes[y].relation;
}
vector<int>V[maxn],IDX,ans;
int number[maxn][2];
int route[maxn][maxn];
int dp[maxn][maxn];
bool vis[maxn];
int main()
{
int n, p1, p2;
while (~scanf("%d %d %d", &n, &p1, &p2) &&(n!=0||p1!=0||p2!=0))
{
memset(vis, 0, sizeof(vis));
memset(number, 0, sizeof(number));
memset(route, -1, sizeof(route));
memset(dp, 0, sizeof(dp));
for (int i = 1;i <= p1 + p2;i++)nodes[i] = node(i, 0), V[i].clear();
IDX.clear();
ans.clear();
int a, b, roota, rootb;
char s[10];
for (int i = 1;i <= n;i++)
{
scanf("%d %d %s", &a, &b, &s);
roota = find(a);
rootb = find(b);
if (roota != rootb)
{
if (s[0] == 'y')
Union(a,b,roota, rootb, 0);
else
Union(a,b,roota, rootb, 1);
}
}
for (int i = 1;i <= p1 + p2;i++)
{
int root = find(i);
V[root].push_back(i);
if (!vis[root])
IDX.push_back(root), vis[root] = 1;
}
int Size = IDX.size();
for (int i = 0;i < Size;i++)
{
int theidx = IDX[i];
int thesize = V[theidx].size();
for (int j = 0;j < thesize;j++)
{
int nodeidx = V[theidx][j];
if (!nodes[nodeidx].relation)
number[theidx][0]++;
else
number[theidx][1]++;
}
}
for (int i = 0;i < Size;i++)
{
if (i == 0)
{
int theidx = IDX[0];
int number0 = number[theidx][0], number1 = number[theidx][1];
dp[0][number0] ++;
route[0][number0] = 0;
dp[0][number1] ++;
route[0][number1] = 1;
}
else
{
int theidx = IDX[i];
int number0 = number[theidx][0], number1 = number[theidx][1];
for (int j = 0;j <= p1 + p2;j++)if (route[i - 1][j] >= 0)
{
dp[i][j + number0] += dp[i - 1][j];
route[i][j + number0] = 0;
dp[i][j + number1] += dp[i - 1][j] ;
route[i][j + number1] = 1;
}
}
}
if (dp[Size - 1][p1] == 1)
{
int num = p1;
int dep = Size - 1;
while (num)
{
int way = route[dep][num];
int theidx = IDX[dep];
for (int i = 0;i < V[theidx].size();i++)
{
int nodeidx = V[theidx][i];
if (nodes[nodeidx].relation == way)
ans.push_back(nodeidx);
}
num -= number[theidx][way];
dep--;
}
sort(ans.begin(), ans.end());
for (int i=0;i<ans.size();i++)
printf("%d\n", ans[i]);
puts("end");
}
else
puts("no");
}
return 0;
}
G - Supermarket
题意大概就是每个东西有个价值和截止日期,每天能卖一个东西,问n天后能获得的最大的价值是多少。
也是看了题解才恍然大悟,先给东西按价值从大到小排序,然后用并查集的目的就是最快找到截止日期前没卖东西的那天。题目和cf的C. String Reconstruction 差不多。。
代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 10005;
int f[maxn];
typedef pair<int, int>pii;
pii nodes[maxn];
bool cmp(pii a, pii b)
{
return a.first > b.first;
}
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void Union(int x, int y)
{
f[x] = y;
}
int main()
{
int n;
while (cin>>n)
{
for (int i = 1;i <= 10000;i++)f[i] = i;
int p, d;
for (int i = 1;i <= n;i++)
scanf("%d %d", &nodes[i].first, &nodes[i].second);
sort(nodes + 1, nodes + 1 + n, cmp);
int ans = 0;
for (int i = 1;i <= n;i++)
{
int theday = nodes[i].second;
int root = find(theday);
if (root > 0)
{
ans += nodes[i].first;
Union(root, root - 1);
}
}
cout << ans << endl;
}
}
H - Parity game
这题思路其实和之前的差不多,然而length不大于1e9。。数组根本开不下去。所以这时候需要离散化后再进行并查集。
不是特别熟悉离散化,所以记录一下这题。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int maxn =5005*4 ;
int f[maxn], sum[maxn];
struct node
{
int u, v, ones;
}nodes[maxn];
vector<int>V;
int find(int a)
{
if (f[a] != a)
{
int pre = f[a];
f[a] = find(f[a]);
sum[a] = sum[a] ^ sum[pre];
}
return f[a];
}
void Union(int u, int v, int rootu, int rootv, int val)
{
f[rootv] = rootu;
sum[rootv] = sum[u] ^ val^sum[v];
}
int main()
{
for (int i = 0;i < maxn;i++)f[i] = i, sum[i] = 0;
int n;
scanf("%d", &n);
scanf("%d", &n);
char s[10];
for (int i = 0;i < n;i++)
{
scanf("%d %d %s", &nodes[i].u, &nodes[i].v, s);
if (s[0] == 'e')nodes[i].ones = 0;
else nodes[i].ones = 1;
V.push_back(nodes[i].u);
V.push_back(nodes[i].v);
V.push_back(nodes[i].u - 1);
V.push_back(nodes[i].v - 1);
}
sort(V.begin(), V.end());
unique(V.begin(), V.end());
int i;
for ( i = 0;i < n;i++)
{
int uidx = lower_bound(V.begin(), V.end(), nodes[i].u - 1) - V.begin();
int vidx = lower_bound(V.begin(), V.end(), nodes[i].v) - V.begin();
int rootu = find(uidx);
int rootv = find(vidx);
if (rootu != rootv)
Union(uidx, vidx, rootu, rootv, nodes[i].ones);
else
{
if ((sum[vidx] ^ sum[uidx]) != nodes[i].ones)
break;
}
}
cout << i << endl;
}
I - Navigation Nightmare
这题比较特别的地方是要开2个关系域,一个代表水平距离,一个代表垂直距离。另外注意方向即可。
#include<iostream>
#include<vector>
#include<cstring>
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
using namespace std;
const int maxn = 40005;
const int maxm = 40005;
int f[maxn], sum1[maxn], sum2[maxn];
struct query
{
int u, v, dis;
char s[3];
}Q[maxm];
typedef pair<int, int>pii;
vector<pii>V[maxm];
int find(int a)
{
if (f[a] != a)
{
int pre = f[a];
f[a] = find(f[a]);
sum1[a] += sum1[pre];
sum2[a] += sum2[pre];
}
return f[a];
}
void Union(int u, int v, int rootu, int rootv, int val, char *s)
{
int tmp1, tmp2;
if (s[0] == 'N')
tmp1 = val, tmp2 = 0;
else if (s[0] == 'E')
tmp1 = 0, tmp2 = val;
else if (s[0] == 'W')
tmp1 = 0, tmp2 = -val;
else
tmp1 = -val, tmp2 = 0;
//if (rootu < rootv)swap(rootu, rootv);
f[rootu] = rootv;
sum1[rootu] = -sum1[u] + tmp1 + sum1[v];
sum2[rootu] = -sum2[u] + tmp2 + sum2[v];
}
int main()
{
int N, M;
scanf("%d %d", &N, &M);
for (int i = 1;i <= N;i++)f[i] = i, sum1[i] = 0, sum2[i] = 0;
for (int i = 1;i <= M;i++)
{
scanf("%d %d %d %s", &Q[i].u, &Q[i].v, &Q[i].dis, Q[i].s);
}
int q;
scanf("%d", &q);
int u, v, idx;
while (q--)
{
scanf("%d %d %d", &u, &v, &idx);
V[idx].push_back(pii(u, v));
}
int rootu, rootv;
for (int i = 1;i <= M;i++)
{
u = Q[i].u, v = Q[i].v;
rootu = find(u);
rootv = find(v);
if (rootu != rootv)Union(u, v, rootu, rootv, Q[i].dis, Q[i].s);
int Size = V[i].size();
for (int j = 0;j < Size;j++)
{
u = V[i][j].first, v = V[i][j].second;
rootu = find(u);
rootv = find(v);
if (rootu != rootv)
puts("-1");
else
{
int dist1 = sum1[u] - sum1[v], dist2 = sum2[u] - sum2[v];
int dis = abs(dist1) + abs(dist2);
printf("%d\n", dis);
}
}
}
return 0;
}
K - Rochambeau
枚举裁判,然后并查集判断
裁判由于可以任意出,所以可能属于任意一个集合,所以有裁判参与的会合不考虑,然后并查集部分和食物链很相似。
如果某个裁判那里出现了矛盾,则记录一下在哪出问题。
然后判断是否只有一个裁判没有出现问题。如果只有一个,说明可以确定,那么就是剩下的人出问题的最大值。枚举裁判,然后并查集判断
裁判由于可以任意出,所以可能属于任意一个集合,所以有裁判参与的会合不考虑,然后并查集部分和食物链很相似。
如果某个裁判那里出现了矛盾,则记录一下在哪出问题。
然后判断是否只有一个裁判没有出现问题。如果只有一个,说明可以确定,那么就是剩下的人出问题的最大值。因为只有否定了其它所有人都不是裁判,才能确定他是裁判。
#include<iostream>
#include<cstring>
#include<stdio.h>
#include<string>
#include<algorithm>
using namespace std;
const int maxn = 505;
const int maxm = 2005;
const int inf = 0x3f3f3f3f;
int f[maxn], sum[maxn];
int find(int a)
{
if (f[a] != a)
{
int pre = f[a];
f[a] = find(f[a]);
sum[a] = (sum[a] + sum[pre]) % 3;
}
return f[a];
}
void Union(int u, int v, int rootu, int rootv, int val)
{
f[rootu] = rootv;
sum[rootu] = (3 - sum[u] + val + sum[v]) % 3;
}
string s[maxm];
int main()
{
int N, M;
while (~scanf("%d %d", &N, &M))
{
for (int i = 0;i < M;i++)
cin >> s[i];
int ansnum = 0, ansidx = 0,ans;
for (int i = 0;i < N;i++)
{
for (int j = 0;j < N;j++)f[j] = j, sum[j] = 0;
//bool flag = true;
int j;
int tmp;
for ( j = 0;j < M;j++)
{
int Size = s[j].size();
int num, u, v, rootu, rootv;
for (int t = 0;t < Size;t++)
{
if(s[j][t]=='=')
{
num = 0;break;
}
else if (s[j][t] == '>')
{
num = 1;break;
}
else if (s[j][t] == '<')
{
num = 2;break;
}
}
if (num == 0)
sscanf(s[j].c_str(), "%d=%d", &u, &v);
else if (num == 1)
sscanf(s[j].c_str(), "%d>%d", &u, &v);
else sscanf(s[j].c_str(), "%d<%d", &u, &v);
if (u == i || v == i)continue;
rootu = find(u);
rootv = find(v);
if (rootu != rootv)
Union(u, v, rootu, rootv, num);
else
{
if ((sum[u] + 3 - sum[v]) % 3 != num)
{
tmp = j + 1;
break;
}
}
}
if (j == M)
{
ansnum++;
ans = i;
}
else
ansidx = max(ansidx, tmp);
}
if (ansnum > 1)
puts("Can not determine");
else if (ansnum == 0)
puts("Impossible");
else printf("Player %d can be determined to be the judge after %d lines\n", ans, ansidx);
}
return 0;
}
M - 小希的迷宫
题目简单但有几个坑点。
1,不能有森林。(博主注意到了)
2,指向自己的应该也算可行。(注意到了,不过好像没这样的数据。。)
3,一开始输入0 0也算。。(很无语。。)
代码如下:
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 100005;
int f[maxn];
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void Union(int a, int b) { if (a > b)swap(a, b);f[b] = a; }
vector<int>V;
int main()
{
int u, v,rootu,rootv;
while (~scanf("%d %d",&u,&v)&&(u!=-1||v!=-1))
{
if (u == 0 && v == 0)
{
puts("Yes");
continue;
}
V.clear();
for (int i = 1;i < maxn;i++)f[i] = i;
rootu = find(u);rootv = find(v);
V.push_back(u);
V.push_back(v);
Union(rootu, rootv);
bool flag = true;
while (~scanf("%d %d",&u,&v)&&(u!=0||v!=0))
{
//if (u == v)continue;
V.push_back(u);
V.push_back(v);
rootu = find(u);
rootv = find(v);
if (rootu == rootv)
{
flag = false;
puts("No");
//break;
}
else
Union(rootu, rootv);
}
if (flag)
{
sort(V.begin(), V.end());
unique(V.begin(), V.end());
int theroot = find(V[0]);
for(int i=1;i<V.size();i++)
if (find(V[i]) != theroot)
{
flag = false;
break;
}
if (flag)
puts("Yes");
else puts("No");
}
}
}
N - Is It A Tree?
这题和上题差不多,但有点细节不同。
1,空树算。
2,指向自己的不算。
3,森林不算。
注意细节就好!
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
const int maxn = 100005;
int f[maxn];
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void Union(int a, int b) { if (a > b)swap(a, b);f[b] = a; }
set<int>V;
void init()
{
for (int i = 1;i < maxn;i++)f[i] = i;
V.clear();
}
int main()
{
int u, v,rootu,rootv;
int cas = 1;
bool flag = 1;
init();
while (~scanf("%d %d",&u,&v)&&(u!=-1||v!=-1))
{
if (u == 0 && v == 0)
{
if (flag)
{
int cnt = 0,tmp;
for (set<int>::iterator it = V.begin();it != V.end();it++)
{
if (it == V.begin())
tmp = find(*it), cnt++;
else if (tmp != find(*it))
cnt++;
}
if (cnt > 1)flag = 0;
}
if (flag)printf("Case %d is a tree.\n", cas++);
else printf("Case %d is not a tree.\n", cas++);
init();
flag = 1;
continue;
}
rootu = find(u);
rootv = find(v);
if (rootu == rootv)
flag = 0;
else
Union(rootu, rootv);
V.insert(u);
V.insert(v);
}
}
哇,做题一定要注意细节,追求1A率!