#1程序自动分析
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设x1,x2,x3…代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x4≠x1,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
Input
第1行包含1个正整数t,表示需要判定的问题个数。注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第1行包含1个正整数n,表示该问题中需要被满足的约束条件个数。接下来n行,每行包括3个整数i,j,e,描述1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若e=1,则该约束条件为xi=xj;若�e=0,则该约束条件为xi≠xj;
Output
输出文件包括t行。
输出文件的第 k行输出一个字符串“ YES” 或者“ NO”(不包含引号,字母全部大写),“ YES” 表示输入中的第k个问题判定为可以被满足,“ NO” 表示不可被满足。
Sample Input
#1
2
2
1 2 1
1 2 0
2
1 2 1
2 1 1
#2
2
3
1 2 1
2 3 1
3 1 1
4
1 2 1
2 3 1
3 4 1
1 4 0
Sample Output
#1
NO
YES
#2
YES
NO
分析
这道题是一道很明显的并查集的题, 也是我做过的最简单的一道并查集 我们对于两个相等的变量就将两个变量加到同一个集合里, 若对于所有“不等”的指令,我们就判断两个数是否在同一个集合里,如果存在则不满足条件。
这道题中x<=1e9, 但是约束条件最大也只有1e5, 所以我们用离散化处理一下。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 1e6+7;
int T, n, flag, cnt;
struct Node{
int x, y, e;
}q[MAXN];
int Tmp[MAXN*3];
int father[MAXN];
//bool comp1 (const int &a, const int &b){ return a < b; }
bool comp (Node const &a, Node const &b) { return a.e > b.e; }
int find(int son)
{
if(son == father[son]) return son;
return father[son] = find(father[son]);
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
cnt = 0;
flag = 1;
memset(q, 0, sizeof(q));
memset(Tmp, 0, sizeof(Tmp));
memset(father, 0, sizeof(father));
for(int i = 1; i <= n; i++)
{
scanf("%d%d%d", &q[i].x, &q[i].y, &q[i].e);
Tmp[++cnt] = q[i].x;
Tmp[++cnt] = q[i].y;
}
sort(Tmp+1, Tmp+cnt+1);
int Len = unique(Tmp+1, Tmp+cnt+1) - Tmp - 1;
for(int i = 1; i <= n; i++)
{
q[i].x = lower_bound(Tmp+1, Tmp+Len+1, q[i].x) - Tmp - 1;
q[i].y = lower_bound(Tmp+1, Tmp+Len+1, q[i].y) - Tmp - 1;
}
for(int i = 1; i <= Len; i++)
father[i] = i;
sort(q+1, q+n+1, comp);
for(int i = 1; i <= n; i++)
{
int fa1 = find(q[i].x);
int fa2 = find(q[i].y);
if(q[i].e)
{
father[fa1] = fa2;
}
else if(fa1 == fa2)
{
printf("NO\n");
flag = 0;
break;
}
}
if(flag) printf("YES\n");
}
return 0;
}
#2银河英雄传说
题目背景
背景请移步洛谷
有很多艘银河战舰,对于这些战舰,有两种指令, M i j 表示将i所在的舰队接到j所在的舰队末尾, C i j 表示查询i与j之间相隔了多少艘战舰。
Input
第一行有一个整数T(1≤T≤500,000),表示总共有T条指令。
以下有T行,每行有一条指令。指令有两种格式:
M i j:i和j是两个整数(1≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第ii号战舰与第jj号战舰不在同一列。
C i j:i和j是两个整数(1≤i,j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。
Output
依次对输入的每一条指令进行分析和处理:
如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;
如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第i号战舰与第j号战舰之间布置的战舰数目。如果第i号战舰与第j号战舰当前不在同一列上,则输出−1。
Sample Input
4
M 2 3
C 1 2
M 2 4
C 4 2
Sample Output
-1
1
分析
这道题题目很明显是一道带权并查集的题, 我们定义一个Dist[]数组, 初始化为0, 两艘战舰之间的距离就为(设为x, y) abs(Dist[x]-Dist[y]) - 1。但是在执行M指令的时候需要注意我们需要保存以y为根的子树的大小,将x合并到y的时候,Dist[x] = Size[y], Size[y] += Size[x]。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 5e5+5;
int n, u, v;
char x;
int fa[MAXN], Dist[MAXN], Size[MAXN];
int find(int son)
{
if(son == fa[son]) return son;
int root = find(fa[son]);
Dist[son] += Dist[fa[son]];
return fa[son] = root;
}
void merge(int x, int y)
{
// x = find(x), y = find(y);
fa[x] = y;
Dist[x] = Size[y];
Size[y] += Size[x];
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
fa[i] = i;
Dist[i] = 0;
Size[i] = 1;
}
for(int i = 1; i <= n; i++)
{
cin >> x >> u >> v;
int fa1 = find(u), fa2 = find(v);
if(x == 'M')
merge(fa1, fa2);
else {
if(fa1 != fa2)
{
printf("-1\n");
continue;
}
printf("%d\n", abs(Dist[u]-Dist[v])-1);
}
}
return 0;
}
#3Parity game
现在小A的手上有一个长度为n(n≤1000000000)的01序列,现在小A给了小Bm个指令形容这个序列,可是我们小B的智商高达1000000%10, 他当然发现小A有可能说谎!那么我们智商高达1000000%10的小B想请智商更高的你来帮助他。如果小A在撒谎,请你帮他求出一个值,满足存在一个序列满足1~k-1个回答, 但不满足1~k个回答。如果小A没有撒谎,就输出m。
Input
第一行一个整数n,表示这个01序列的长度。
接下来一行一个整数m,表示有m个指令。
接下来m行,每行的指令形如l, r, even/odd
even代表[l, r]这个区间内有偶数个1, odd表示[l, r]这个区间内有奇数个1。
Output
一个整数
Sample Input
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
Sample Output
3
边带权做法分析
看到这道题,我们第一步应该想到使用并查集+离散化, 然后我们可以得出三个简单易懂的结论:
1.若x1与x2奇偶性相同, x2与x3奇偶性相同,那么x1与x3奇偶性相同。
2.若x1与x2奇偶性相同, x2与x3奇偶性不同,那么x1与x3奇偶性不同。
3.若x1与x2奇偶性不同, x2与x3奇偶性不同,那么x1与x3奇偶性相同。
这样我们就可以得出下面两个结论:
1.[l - r]有偶数个1,就是[1 - l-1]和[l - r]奇偶性相同。
2.[l - r]有奇数个1, 就是[1 - l-1]和[l - r]奇偶性不同。
于是我们用Dist[x] = 0表示x与fa[x]奇偶性相同,Dist[x] = 1表示x与fa[x]奇偶性不同。路径压缩时就用异或运算。
对于第i个l和r,设它们离散化后分别对应x和y,这个区间中有Num个1(0为偶数个,1为奇数个), 若find(x) == find(y), 则判断Dist[x] ^ Dist[y]是否等于Num就行了。若他们不在一个集合内,find[x] = p, find[y] = q; fa[p] = q;Dist[p] = Dist[x] ^ Dist[y] ^ Num;
STD
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 2e4+5;
int n, m, cnt, Ans;
char Str[5];
struct Node {
int Left, Right;
int Num;
}q[MAXN];
int Tmp[MAXN], fa[MAXN], Dist[MAXN];
int find (int son)
{
if(son == fa[son]) return son;
int root = find(fa[son]);
Dist[son] ^= Dist[fa[son]];
return fa[son] = root;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
{
scanf("%d%d%s", &q[i].Left, &q[i].Right, &Str);
q[i].Num = (Str[0]=='o' ? 1:0);
Tmp[++cnt] = q[i].Left-1;
Tmp[++cnt] = q[i].Right;
}
sort(Tmp+1, Tmp+cnt+1);
n =unique(Tmp+1, Tmp+cnt+1) - Tmp - 1;
for(int i = 1; i <= n; i++)
fa[i] = i;
for(int i = 1; i <= m; i++)
{
int x = lower_bound(Tmp+1, Tmp+n+1, q[i].Left-1) - Tmp;
int y = lower_bound(Tmp+1, Tmp+n+1, q[i].Right) - Tmp;
int fa1 = find(x), fa2 = find(y);
if(fa1 == fa2)
{
if( (Dist[x]^Dist[y]) != q[i].Num)
{
printf("%d", i-1);
return 0;
}
}
else {
fa[fa1] = fa2;
Dist[fa1] = Dist[x] ^ Dist[y] ^ q[i].Num;
}
}
printf("%d", m);
return 0;
}
扩展域做法分析
把每个节点x拆成两个节点x_odd表示x的偶数域, x_even表示x的奇数域。
若Num = 0, 合并x_odd和y_odd,x_even和y_even。
若Num = 1, 合并x_odd和y_even, x_even和y_odd。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 2e4+5;
int n, m, cnt, Ans;
char Str[5];
struct Node {
int Left, Right;
int Num;
}q[MAXN];
int Tmp[MAXN], fa[MAXN], Dist[MAXN];
int find (int son)
{
if(son == fa[son]) return son;
return fa[son] = find(fa[son]);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
{
scanf("%d%d%s", &q[i].Left, &q[i].Right, &Str);
q[i].Num = (Str[0]=='o' ? 1:0);
Tmp[++cnt] = q[i].Left-1;
Tmp[++cnt] = q[i].Right;
}
sort(Tmp+1, Tmp+cnt+1);
n =unique(Tmp+1, Tmp+cnt+1) - Tmp - 1;
for(int i = 1; i <= 2*n; i++)
fa[i] = i;
for(int i = 1; i <= m; i++)
{
int x = lower_bound(Tmp+1, Tmp+n+1, q[i].Left-1) - Tmp;
int y = lower_bound(Tmp+1, Tmp+n+1, q[i].Right) - Tmp;
int x_odd = x, x_even = x+n;
int y_odd = y, y_even = y+n;
if(q[i].Num == 0)
{
if(find(x_odd) == find(y_even))
{
printf("%d", i-1);
return 0;
}
fa[find(x_odd)] = find(y_odd);
fa[find(x_even)] = find(y_even);
} else {
if(find(x_odd) == find(y_odd))
{
printf("%d", i-1);
return 0;
}
fa[find(x_odd)] = find(y_even);
fa[find(x_even)] = find(y_odd);
}
}
printf("%d", m);
return 0;
}
总结
个人拓展域有些时候比边带权要好做一些,但是消耗的空间更大。
#4 关押罪犯[NOIP2010]
题目
S城现有两座监狱,一共关押着N名罪犯,编号分别为1-N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c的冲突事件。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
Input
第一行为两个正整数N,MN,M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的MM行每行为三个正整数a_j,b_j,c_j,表示a_j号和b_j 号罪犯之间存在仇恨,其怨气值为c_j 。数据保证 1<aj≤bj≤N,0<cj≤1,000,000,000,且每对罪犯组合只出现一次。
Output
共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。
Sample Input
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
Sample Output
3512
分析
这道题是一道扩展域并查集,我们要尽量让两个怨气值大的罪犯在两个监狱,假如现在有一个人 i,还有一个人叫 j,如果 i 和 j 的怨气值很高,那么 i 和 j 肯定不能在一个监狱,那么和 j 在一个监狱的罪犯也不能和 i 在一个监狱(李某东 : 请读者自行证明此结论)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+5;
ll n, m;
int fa[MAXN];
struct name {
ll f, s, c;
}Tree[MAXN];
bool comp(name x, name y){ return x.c > y.c; }
int find(int x)
{
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
int merge(int x,int y)
{
int ff=find(x),fff=find(y);
fa[ff]=fff;
}
int main()
{
// freopen("prison.in","r",stdin);
// freopen("prison.out","w",stdout);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= 2*n; i++)
fa[i] = i;
for(int i = 1; i <= m; i++)
scanf("%lld%lld%lld", &Tree[i].f, &Tree[i].s, &Tree[i].c);
sort(Tree+1, Tree+m+1, comp);
for(int i = 1; i <= m; i++)
{
int x = find(Tree[i].f), y = find(Tree[i].s);
if(x == y)
{
cout << Tree[i].c;
return 0;
}
merge(Tree[i].f, Tree[i].s+n);
merge(Tree[i].s, Tree[i].f+n);
}
cout << 0;
return 0;
}