Parity Game POJ1733
问题描述
链接:https://ac.nowcoder.com/acm/contest/1031/C
来源:牛客网
题目描述
Now and then you play the following game with your friend. Your friend writes down a sequence consisting of zeroes and ones. You choose a continuous subsequence (for example the subsequence from the third to the fifth digit inclusively) and ask him, whether this subsequence contains even or odd number of ones. Your friend answers your question and you can ask him about another subsequence and so on. Your task is to guess the entire sequence of numbers.
You suspect some of your friend's answers may not be correct and you want to convict him of falsehood. Thus you have decided to write a program to help you in this matter. The program will receive a series of your questions together with the answers you have received from your friend. The aim of this program is to find the first answer which is provably wrong, i.e. that there exists a sequence satisfying answers to all the previous questions, but no such sequence satisfies this answer.
输入描述:
The first line of input contains one number, which is the length of the sequence of zeroes and ones. This length is less or equal to 1000000000. In the second line, there is one positive integer which is the number of questions asked and answers to them. The number of questions and answers is less or equal to 5000. The remaining lines specify questions and answers. Each line contains one question and the answer to this question: two integers (the position of the first and last digit in the chosen subsequence) and one word which is either 'even' or 'odd' (the answer, i.e. the parity of the number of ones in the chosen subsequence, where 'even' means an even number of ones and 'odd' means an odd number).
输出描述:
There is only one line in output containing one integer X. Number X says that there exists a sequence of zeroes and ones satisfying first X parity conditions, but there exists none satisfying X+1 conditions. If there exists a sequence of zeroes and ones satisfying all
the given conditions, then number X should be the number of all the questions asked.
示例1
输入
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
输出
3
问题大意
有一个长为S的01序列,长度为N,(\(N\le10^9\))有M个(\(M\le5000\))关于01序列的描述,每个描述包括问题和回答,问题指定两个数l和r,回答是S[l ~ r]中有奇数个1还是偶数个1。求最大的X,存在S使得回答1~X可以同时成立,但回答1 ~ X+1不同时不成立。
问题分析
我们可以用sum数组表示S的前缀和,关于每个回答:
- S[l~r]有偶数个1,等价于S[l-1]与S[r]奇偶性相同
- S[l~r]有奇数个1,等价于S[l-1]与S[r]奇偶性不同
题目并没有要求求出S,所以并不用求出sum数组,我们只需利用问题间的传递关系:
- \(x_1\)与\(x_2\)奇偶性相同,\(x_2\)与\(x_3\)奇偶性相同,那么\(x_1\)与\(x_3\)的奇偶性也相同
- \(x_1\)与\(x_2\)奇偶性相同,\(x_2\)与\(x_3\)奇偶性不同,那么\(x_1\)与\(x_3\)的奇偶性不同
- \(x_1\)与\(x_2\)奇偶性不同,\(x_2\)与\(x_3\)奇偶性不同,那么\(x_1\)与\(x_3\)的奇偶性相同
由于序列长度很大,问题数M很少,可以用离散化的方法将l-1和r映射到1~2M的范围
struct {
int l, r, ans;
} query[10005];
int a[10005], fa[10005], d[10005];
int n, m, t;
void read_discrete() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
char s[6];
scanf("%d%d%s", &query[i].l, &query[i].r, s);
query[i].ans = s[0] == 'o' ? 1 : 0;
a[++t] = query[i].l - 1;
a[++t] = query[i].r;
}
sort(a + 1, a + t + 1);
n = unique(a + 1, a + t + 1) - a - 1;
}
解法1 边带权并查集
边权d[x]为0表示x与fa[x]奇偶性相同,为1表示x与fa[x]奇偶性不同,路径压缩时,将x到树根所有的边权作异或运算即可得到x与树根的奇偶性关系。
对于每个问题,设离散化后l-1和r的值分别为x和y,ans表示该问题的回答,1为奇数个0为偶数个。
先检查x和y的关系是否已知,即当get(x)等于get(y)时,x和y的关系已知,d[x] xor d[y] 为x和y的奇偶关系,与ans比较,若不同则说明此时无法满足关系。
若x和y不在一个集合,需要合并两个集合的树根(设为p和q),令p为q的子节点,现已知x到y的路径上所有边权异或为ans,d[x]为x到p的边权,d[y]为y到q的边权(或所有边权异或的值),路径xy由xp,pq,qy组成,显然合并后有ans = d[x] ^ d[y] ^ d[p],两边异或d[x] ^ d[y]即可得到d[p] = d[x] ^ d[y] ^ ans
代码如下:
int get(int x) {
if (x == fa[x]) return x;
int root = get(fa[x]);
d[x] ^= d[fa[x]];
return fa[x] = root;
}
int main() {
read_discrete();
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++) {
// 求l - 1 和 r 离散化后的值
int x = lower_bound(a + 1, a + n + 1, query[i].l - 1) - a;
int y = lower_bound(a + 1, a + n + 1, query[i].r) - a;
// 得到二者树根
int p = get(x), q = get(y);
if (p == q) {
if ((d[x] ^ d[y]) != query[i].ans) {
cout << i - 1 << endl;
return 0;
}
} else { // 不再统一集合内
fa[p] = q;
d[p] = d[x] ^ d[y] ^ query[i].ans;
}
}
cout<<m<<endl;
}
解法2 扩展域并查集
对一个结点x,sum[x]有可能为奇数也有可能为偶数,边带权的做法是以边权表示这种关系。
把每个x用两个结点\(x_{odd}\)和\(x_{even}\)表示,其中\(x_{odd}\)表示sum[x]为奇数,\(x_{even}\)表示sum[x]为偶数,这两个结点称为x的“奇数域”和“偶数域”。
对每个问题,x、y分别表示l-1和r离散化后的值,ans表示问题答案(1为奇数,0为偶数)
- ans=0,合并\(x_{odd}\),\(y_{odd}\)与\(x_{even}\)和\(y_{even}\),表示“x为奇数”与“y为奇数”等价,“x为偶数”与“y为偶数”等价。
- ans=1,合并\(x_{odd}\),\(y_{even}\)与\(x_{even}\)和\(y_{odd}\),表示“x为奇数”与“y为偶数”等价,“x为偶数”与“y为奇数”等价。
对每个问题先检查是否矛盾,如果\(x_{odd}\)与\(y_{odd}\)节点在一个集合内,则二者奇偶性相同,此时ans为1则说明矛盾,否则合并x、y,对\(x_{odd}\),\(y_{even}\)节点在一个集合内的情况,做类似处理。做法如下:
int get(int x) {
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
int main() {
read_discrete();
for (int i = 1; i <= 2 * n; i++) fa[i] = i; // 扩展域后为2n
for (int i = 1; i <= m; i++) {
// 求l - 1 和 r 离散化后的值
int x = lower_bound(a + 1, a + n + 1, query[i].l - 1) - a;
int y = lower_bound(a + 1, a + n + 1, query[i].r) - a;
int x_odd = x, x_even = x + n;
int y_odd = y, y_even = y + n;
if (query[i].ans) {
if (get(x_odd) == get(y_odd)) {
cout << i - 1 << endl;
return 0;
}
fa[get(x_odd)] = get(y_even);
fa[get(x_even)] = get(y_odd);
} else {
if (get(x_odd) == get(y_even)) {
cout << i - 1 << endl;
return 0;
}
fa[get(x_odd)] = get(y_odd);
fa[get(x_even)] = get(y_even);
}
}
cout << m << endl;
}
浙公网安备 33010602011771号