POJ 1182 题目链接
题意
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
思路1
首
先
每
种
动
物
都
有
三
种
可
能
,
我
们
用
d
[
3
n
]
表
示
三
种
类
型
首先每种动物都有三种可能,我们用d[3n]表示三种类型
首先每种动物都有三种可能,我们用d[3n]表示三种类型
d
[
i
]
表
示
动
物
i
为
A
,
d
[
i
+
n
]
表
示
动
物
i
为
B
,
d
[
i
+
2
n
]
表
示
动
物
i
为
C
d[i]表示动物i为A,d[i + n]表示动物i为B,d[i + 2n]表示动物i为C
d[i]表示动物i为A,d[i+n]表示动物i为B,d[i+2n]表示动物i为C
如
果
i
,
j
两
个
种
类
相
同
,
那
么
不
管
i
是
那
种
类
型
,
j
也
应
该
是
那
种
类
型
,
即
:
如果i,j两个种类相同,那么不管i是那种类型,j也应该是那种类型,即:
如果i,j两个种类相同,那么不管i是那种类型,j也应该是那种类型,即:
d
[
i
]
=
d
[
j
]
,
d
[
i
+
n
]
=
d
[
j
+
n
]
,
d
[
i
+
2
n
]
=
d
[
j
+
2
n
]
d[i] = d[j],d[i+n]=d[j+n],d[i+2n]=d[j+2n]
d[i]=d[j],d[i+n]=d[j+n],d[i+2n]=d[j+2n]
如
果
i
吃
j
,
那
么
就
有
(
A
,
B
)
,
(
B
,
C
)
,
(
C
,
A
)
三
种
可
能
,
即
:
如果i吃j,那么就有(A,B),(B,C),(C,A)三种可能,即:
如果i吃j,那么就有(A,B),(B,C),(C,A)三种可能,即:
d
[
i
]
=
d
[
j
+
n
]
,
d
[
i
+
n
]
=
d
[
j
+
2
n
]
,
d
[
i
+
2
n
]
=
d
[
j
]
d[i] = d[j+n],d[i+n]=d[j+2n],d[i+2n]=d[j]
d[i]=d[j+n],d[i+n]=d[j+2n],d[i+2n]=d[j]
也
就
是
说
如
果
(
i
,
j
)
在
同
一
个
集
合
中
,
就
表
示
,
i
,
j
同
类
也就是说如果(i,j)在同一个集合中,就表示,i,j同类
也就是说如果(i,j)在同一个集合中,就表示,i,j同类
如
果
(
i
,
j
+
n
)
在
同
一
个
集
合
中
,
就
表
示
i
吃
j
如果(i,j+n)在同一个集合中,就表示i吃j
如果(i,j+n)在同一个集合中,就表示i吃j
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <utility>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 5e4 + 5;
const int mod = 10007;
int fa[N * 3];
int Find(int x)
{
return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
void Union(int a,int b)
{
int x = Find(a),y = Find(b);
if (x != y){
fa[x] = y;
}
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for (int i = 1; i <= 3 * n; i++){
fa[i] = i;
}
int ans = 0;
for (int i = 0; i < m; i++){
int op,x,y;
scanf("%d %d %d",&op,&x,&y);
if (x > n || y > n) ans++; /// 第二个条件
else {
if (op == 1){
if (Find(x) == Find(y + n) || Find(y) == Find(x + n)) ans++; /// x吃y,或者y吃x,第一个条件
else {
Union(x,y);
Union(x + n,y + n);
Union(x + 2 * n,y + 2 * n);
}
}
else {
if (Find(y) == Find(x + n) || Find(x) == Find(y)) ans++; /// y吃x,x,y是同一类,第三个条件
else {
Union(x,y + n);
Union(x + n,y + 2 * n);
Union(x + 2 * n,y);
}
}
}
}
cout << ans << endl;
return 0;
}
思路2
首
先
用
d
[
x
]
表
示
x
与
其
父
亲
结
点
的
关
系
首先用d[x]表示x与其父亲结点的关系
首先用d[x]表示x与其父亲结点的关系
d
[
x
]
有
三
种
权
值
,
0
代
表
同
类
,
1
代
表
父
亲
结
点
吃
x
,
2
代
表
x
吃
其
父
亲
结
点
d[x]有三种权值,0代表同类,1代表父亲结点吃x,2代表x吃其父亲结点
d[x]有三种权值,0代表同类,1代表父亲结点吃x,2代表x吃其父亲结点
如
下
图
,
如
果
x
,
y
不
同
父
亲
结
点
,
根
据
矢
量
关
系
,
我
们
就
能
求
出
d
[
f
y
]
=
d
[
x
]
+
t
y
p
e
−
1
−
d
[
y
]
如下图,如果x,y不同父亲结点,根据矢量关系,我们就能求出d[fy] = d[x] + type - 1 - d[y]
如下图,如果x,y不同父亲结点,根据矢量关系,我们就能求出d[fy]=d[x]+type−1−d[y]
如
果
x
,
y
相
同
父
亲
结
点
,
那
么
更
新
之
后
的
d
[
y
]
=
d
[
y
]
+
d
[
f
y
]
,
所
以
d
[
y
]
−
d
[
x
]
=
=
t
y
p
e
−
1
如果x,y相同父亲结点,那么更新之后的d[y] = d[y]+d[fy],所以d[y] - d[x] == type - 1
如果x,y相同父亲结点,那么更新之后的d[y]=d[y]+d[fy],所以d[y]−d[x]==type−1
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <utility>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 5e4 + 5;
const int mod = 10007;
int fa[N];
int d[N];
int Find(int x)
{
if (fa[x] == x) return x;
int head = Find(fa[x]);
d[x] = (d[x] + d[fa[x]]) % 3;
return fa[x] = head;
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
int ans = 0;
for (int i = 1; i <= n; i++){
d[i] = 0;
fa[i] = i;
}
for (int i = 0; i < m; i++){
int type,x,y;
scanf("%d %d %d",&type,&x,&y);
if (x > n || y > n) ans++;
else {
int fx = Find(x),fy = Find(y);
if (fx != fy){
fa[fy] = fx;
d[fy] = (d[x] + type - 1 - d[y] + 3) % 3;
}
else if ((d[y] - d[x] + 3) % 3 != type - 1) ans++;
}
}
cout << ans << endl;
return 0;
}
POJ 1417 题目链接
题意
一共有p1个好人,p2个坏人,给你n句话
x1 x2 yes (x1说x2是好人)
或者
x1 x2 no (x1说x2不是好人)
坏人只会说假话,好人只会说真话,问你能够找出p1个好人吗?
思路
如果是x1 x2 yes (x1说x2是好人),则说明,x1,x2是同一类人
反之则不是同类人(想一想)
根据并查集,我们可以把p1+p2个人分成好几个集合,每一个集合都只有两类人(一个集合中的第一类人不一定等同于另一个集合的第一类人)
我们要做的就是从每一个集合中挑选某一类人凑够p1即可
我们可以用01背包来做
只有dp[cnt][p1] == 1 才能有解(cnt代表集合的个数,大于1的话就是情况不唯一,也不行)
最后再逆向跑一遍背包输出路径
详情看代码
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
const int N = 666;
const int M = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
char str[6];
int fa[N],d[N];
int bag[N][2];
int id[N],cnt;
int dp[N][N];
int choose[N];
int Find(int x)
{
if (fa[x] == x) return x;
int cur = Find(fa[x]);
d[x] = (d[x] + d[fa[x]]) % 2;
return fa[x] = cur;
}
void Union(int x,int y,int value)
{
int fx = Find(x),fy = Find(y);
if (fx != fy){
fa[fy] = fx;
d[fy] = (d[x] - d[y] + value + 2) % 2;
}
}
void init(int x,int dv)
{
if (id[x] == -1) id[x] = ++cnt;
bag[id[x]][dv]++;
}
int main()
{
int n,p1,p2;
while(scanf("%d %d %d",&n,&p1,&p2) == 3 && (n || p1 || p2)){
for (int i = 1; i <= p1 + p2; i++){
fa[i] = i;
d[i] = 0;
}
for (int i = 0; i < n; i++){
int x,y,value;
scanf("%d %d %s",&x,&y,str);
if (str[0] == 'y') value = 0;
else value = 1;
Union(x,y,value);
}
cnt = 0;
memset(id,-1,sizeof(id));
memset(bag,0,sizeof(bag));
for (int i = 1; i <= p1 + p2; i++){
int fx = Find(i);
init(fx,d[i]);
}
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
for (int i = 1; i <= cnt; i++){
for (int j = p1; j >= 0; j--){
if (j >= bag[i][0]){
dp[i][j] = dp[i - 1][j - bag[i][0]];
}
if(j >= bag[i][1]){
dp[i][j] += dp[i - 1][j - bag[i][1]];
}
}
}
if (dp[cnt][p1] == 1) {
int j = p1;
memset(choose,-1,sizeof(choose));
for (int i = cnt; i >= 1; i--){
if (dp[i][j] == dp[i - 1][j - bag[i][0]]){
choose[i] = 0;
j -= bag[i][0];
}
else if (dp[i][j] == dp[i - 1][j - bag[i][1]]){
choose[i] = 1;
j -= bag[i][1];
}
}
for (int i = 1; i <= p1 + p2; i++){
int fx = Find(i);
if (choose[id[fx]] == d[i]){
printf("%d\n",i);
}
}
printf("end\n");
}
else printf("no\n");
}
return 0;
}
POJ 1984 题目链接
题意
有n个地点
m条路径:x1 x2 dis to (x2 在 x1的 to方向,距离为dis)
接着给出q个询问: x1 x2 t(询问在给出第t条路径的时候,x1,x2之间的曼哈顿距离,不能判断两点的距离就输出-1)
思路
首先可以根据并查集的向量操作,求出相连点(直接或间接)之间任意两点的距离
接着我们把询问都存起来,按顺序依次进行m条路径的合并操作,到达询问点后就把该询问点的所有询问都求出来
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 4e4 + 5;
const int M = 4e4 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
struct query{
int x,y,index;
};
struct node{
int x,y,dis;
char to;
}s[N];
int dx[N],dy[N];
int id[N],ans[N];
int fa[N];
vector<query> v[N];
int Find(int x)
{
if (fa[x] == x) return x;
int head = Find(fa[x]);
dx[x] = dx[x] + dx[fa[x]];
dy[x] = dy[x] + dy[fa[x]];
return fa[x] = head;
}
void Union(int x,int y,int dis,int value)
{
int fx = Find(x),fy = Find(y);
if (fx != fy){
if (value == 0){
fa[fx] = fy;
dx[fx] = dx[y] - dx[x];
dy[fx] = dy[y] + dis - dy[x];
}
else {
fa[fx] = fy;
dx[fx] = dx[y] + dis - dx[x];
dy[fx] = dy[y] - dy[x];
}
}
}
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for (int i = 1; i <= m; i++){
scanf("%d %d %d %c",&s[i].x,&s[i].y,&s[i].dis,&s[i].to);
}
int q;
scanf("%d",&q);
for (int i = 0; i < q; i++){
int x,y,op;
scanf("%d %d %d",&x,&y,&op);
id[i] = op;
v[op].push_back({x,y,i});
}
sort(id,id + q);
int len = unique(id,id + q) - id;
int x = 0;
for (int i = 0; i <= n; i++){
fa[i] = i;
}
for (int i = 1; i <= m; i++){
if (s[i].to == 'N' || s[i].to == 'W') swap(s[i].x,s[i].y);
int value;
if (s[i].to == 'N' || s[i].to == 'S') value = 0;
else value = 1;
Union(s[i].x,s[i].y,s[i].dis,value);
if (x == len) continue;
if (id[x] == i){
for (int j = 0; j < (int)v[i].size(); j++){
int x = v[i][j].x;
int y = v[i][j].y;
int index = v[i][j].index;
int fx = Find(x),fy = Find(y);
if (fx != fy) ans[index] = -1;
else ans[index] = abs(dx[x] - dx[y]) + abs(dy[x] - dy[y]);
}
x++;
}
}
for (int i = 0; i < q; i++){
printf("%d\n",ans[i]);
}
return 0;
}
POJ 2912 题目链接
题意
n个人进行m轮剪刀石头布游戏(0<n<=500,0<=m<=2000),接下来m行形如x, y, ch的输入,ch=’=‘表示x, y平局,ch=’>‘表示x赢y,ch=’<'表示x输y, 但是我们不知道x, y的手势是什么; 其中有一个人是裁判,它可以出任意手势,其余人手势相同的分一组,共分为三组,可以存在空组,也就是说除了裁判外,其余人每一次出的手势都相同,问能不能确定裁判是几号,如果能,输出最少在第几轮可以确定;
思路
我们使用排除法枚举每一个人,如果排除这个人后没有错误,那这个人就是裁判,如果有多个裁判,那就不能确定
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 4e4 + 5;
const int M = 4e4 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
struct node{
int x,y;
char ch;
}s[N];
int fa[N];
int d[N];
int vis[N];
int Find(int x)
{
if (fa[x] == x) return x;
int head = Find(fa[x]);
d[x] = (d[x] + d[fa[x]]) % 3;
return fa[x] = head;
}
bool Union(int x,int y,int value)
{
int fx = Find(x),fy = Find(y);
if (fx == fy){
if ((d[x] - value + 3) % 3 != d[y]) return false;
return true;
}
else {
fa[fx] = fy;
d[fx] = (d[y] + value - d[x] + 3) % 3;
return true;
}
}
void init(int n)
{
for (int i = 0; i < n; i++){
fa[i] = i;
d[i] = 0;
}
}
int main()
{
int n,m;
while(scanf("%d %d",&n,&m) == 2){
for (int i = 0; i < m; i++){
scanf("%d %c %d",&s[i].x,&s[i].ch,&s[i].y);
}
int cnt = 0,ans,mx = 0;
for (int i = 0; i < n; i++){
bool flag = false;
init(n);
for (int j = 0; j < m; j++){
if (s[j].x == i || s[j].y == i) continue;
int x = s[j].x, y = s[j].y,value;
if (s[j].ch == '=') value = 0;
else if (s[j].ch == '>') value = 1;
else value = 2;
if (!Union(x,y,value)){
flag = true;
mx = max(mx,j + 1);
break;
}
}
if (!flag){
ans = i;
cnt++;
}
}
if (cnt == 0) printf("Impossible\n");
else if (cnt == 1) printf("Player %d can be determined to be the judge after %d lines\n",ans,mx);
else printf("Can not determine\n");
}
return 0;
}
ZOJ 3261 题目链接
题意
有
n
个
星
球
,
每
个
星
球
都
有
一
个
权
值
v
i
,
m
个
边
连
接
这
些
星
球
(
直
接
或
者
间
接
)
有n个星球,每个星球都有一个权值v_i,m个边连接这些星球(直接或者间接)
有n个星球,每个星球都有一个权值vi,m个边连接这些星球(直接或者间接)
有
q
个
询
问
:
d
e
s
t
r
o
y
:
a
,
b
,
摧
毁
a
b
之
间
的
边
,
有q个询问:destroy:a,b,摧毁ab之间的边,
有q个询问:destroy:a,b,摧毁ab之间的边,
q
u
e
r
y
:
a
,
查
询
与
a
相
连
(
直
接
或
间
接
)
的
最
大
能
量
星
球
,
如
果
有
多
个
,
则
取
编
号
最
小
那
个
query :a,查询与a相连(直接或间接)的最大能量星球,如果有多个,则取编号最小那个
query:a,查询与a相连(直接或间接)的最大能量星球,如果有多个,则取编号最小那个
如
果
查
询
不
到
比
他
大
的
能
量
星
球
则
结
果
为
−
1
如果查询不到比他大的能量星球则结果为-1
如果查询不到比他大的能量星球则结果为−1
思路
并查集可以O(1)的查询到最大能量星球,但是因为并查集只能添加关系,不能去掉关系;
所以我们要存下所有询问,先把询问中没有涉及到的边先合并,接着逆向建立并查集查询即可(如果有一个点是在摧毁之后进行询问,那么我们逆向之后是先询问这个点再进行连边,所以不会有影响,反之一个点在摧毁之前询问,那么我们是先连边再询问,保证了这条边是存在的)
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e4 + 5;
const int M = 4e4 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
struct node{
int x,y;
char str[11];
}query[N];
int fa[N];
int ans[N];
int v[N];
map<pair<int,int>,bool> mp;
int Find(int x)
{
if (fa[x] == x) return x;
return fa[x] = Find(fa[x]);
}
void Union(int x,int y)
{
int fx = Find(x),fy = Find(y);
if (fx != fy){
if (v[fx] > v[fy] || (v[fx] == v[fy] && fx < fy)) fa[fy] = fx;
else fa[fx] = fy;
}
}
int main()
{
int n,flag = 1;
while(scanf("%d",&n) == 1){
if (!flag) printf("\n");
else flag = 0;
for (int i = 0; i < n; i++){
scanf("%d",&v[i]);
fa[i] = i;
}
int m;
scanf("%d",&m);
mp.clear();
for (int i = 0; i < m; i++){
int x,y;
scanf("%d %d",&x,&y);
if (x > y) swap(x,y);
mp[{x,y}] = true;
}
int q;
scanf("%d",&q);
for (int i = 0; i < q; i++){
scanf("%s",query[i].str);
if (query[i].str[0] == 'd'){
scanf("%d %d",&query[i].x,&query[i].y);
if (query[i].x > query[i].y) swap(query[i].x,query[i].y);
mp[{query[i].x,query[i].y}] = false;
}
else scanf("%d",&query[i].x);
}
for (auto k : mp){
if (k.second){
Union(k.first.first,k.first.second);
}
}
for (int i = q - 1; i >= 0; i--){
ans[i] = -2;
if (query[i].str[0] == 'q'){
int fx = Find(query[i].x);
if (v[fx] == v[query[i].x]) ans[i] = -1;
else ans[i] = fx;
}
else Union(query[i].x,query[i].y);
}
for (int i = 0; i < q; i++){
if (ans[i] != -2) printf("%d\n",ans[i]);
}
}
return 0;
}