P3007 The Continental Cowngress G 做题记录
一道还算板的题。
我们看到,这题干跟模板题没啥区别,唯一的区别是输出方案如果两者皆可输出 ?。
我们来想想怎样才能两者皆可,首先我们想想判断无解是怎么样的:两者在同一强连通分量里,而强连通分量的编号相当于拓扑序,我们要去拓扑序大的即强连通分量编号小的为答案,但是另一个难道就不能是答案了吗?有可能的。而我们发现,之前排除一个答案的方式是什么?往兄弟点连边,只有有边连向兄弟点的点才不能选,选强连通分量编号小的那个点只能保证答案一定不错。那么另一个点没连向兄弟边仍然能选,那么我们怎么办?我们发现数据范围很小,那么我们最后判断输出是像这样判断另一个是否可行就可以了:
for (rnt i = 1; i <= n; i++)
if (num[i] < num[i + n])
if (dfs(num[i + n], num[i]))
cout << "Y";
else
cout << "?";
else if (dfs(num[i], num[i + n]))
cout << "N";
else
cout << "?";
另外,这道题要用上 tarjan 的缩点,缩点建边有两种方式,一种开新数组存边,优点是常数小,但是空间大且用新数组不顺手(你每次多打个下划线试试)
belike:
inline void _add(int u, int v)
{
_e[++cnt] = {v, _head[u]}, _head[u] = cnt;
}
for (rnt u = 1; u <= 2 * n; u++)
for (rnt i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
int fu = num[u], fv = num[v];
if (fu != fv)
_add(fu, fv);//这里用的是新数组
}
多打下划线很方便吧……
还有一种就是记录原来的边数,清空 head 数组和 cnt ,然后枚举每条边,优点是空间小,用起来顺手,缺点是常数要大一点。
belike:
m = cnt, cnt = 0;
for (rnt i = 1; i <= 2 * n; i++)
head[i] = 0;
for (rnt i = 1; i <= m; i++)
{
int fu = num[e[i].u], fv = num[e[i].v];
if (fu != fv)
add(fu, fv);
}
另外不需要什么互不相交集合联合来存储每个点连了哪些点,所有不同的点相连的边都连上,还是一棵树的。
代码:
//Just Sayori
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <random>
#define ll long long
#define rnt register int
#define gr getchar
#define pr putchar
#define N 100005
#define M 1000000007
using namespace std;
inline ll read()
{
ll x = 0, f = 1;
char ch = gr();
while (ch < '0' || ch > '9')
ch == '-' ? f = -1, ch = gr() : ch = gr();
while (ch >= '0' && ch <= '9')
x = (x << 3) + (x << 1) + (ch ^ 48), ch = gr();
return x * f;
}
inline void write(ll x)
{
static int sta[39], top = 0;
if (x < 0)
pr('-'), x = -x;
do
sta[++top] = x % 10, x /= 10;
while (x);
while (top)
pr(sta[top--] ^ 48);
}
struct edge
{
int v, next;
} e[N << 1], _e[N << 1];
int head[N], cnt, _head[N];
inline void add(int u, int v)
{
e[++cnt] = {v, head[u]}, head[u] = cnt;
}
inline void _add(int u, int v)
{
_e[++cnt] = {v, _head[u]}, _head[u] = cnt;
}
int n, m, a, b, c, x, y, bcc, top;
int dfn[N], low[N], num[N], vis[N], stack[N];
char ch1, ch2;
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt;
vis[u] = 1, stack[++top] = u;
for (rnt i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (vis[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u])
{
bcc++;
while (stack[top + 1] != u)
num[stack[top]] = bcc, vis[stack[top--]] = 0;
}
}
bool dfs(int u, int s)
{
if (u == s)
return 1;
for (rnt i = _head[u]; i; i = _e[i].next)
{
int v = _e[i].v;
if (dfs(v, s))
return 1;
}
return 0;
}
int main()
{
cin.tie(0);
//ios::sync_with_stdio(0);
n = read(), m = read();
for (rnt i = 1; i <= m; i++)
{
a = read(), cin >> ch1, x = (ch1 == 'Y'), b = read(), cin >> ch2, y = (ch2 == 'Y');
add(a + (x & 1)*n, b + (y ^ 1)*n), add(b + (y & 1)*n, a + (x ^ 1)*n);
}
for (rnt i = 1; i <= 2 * n; i++)
if (!dfn[i])
tarjan(i);
for (rnt i = 1; i <= n; i++)
if (num[i] == num[i + n])
return puts("IMPOSSIBLE"), 0;
cnt = 0;
for (rnt u = 1; u <= 2 * n; u++)
for (rnt i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
int fu = num[u], fv = num[v];
if (fu != fv)
_add(fu, fv);
}
for (rnt i = 1; i <= n; i++)
if (num[i] < num[i + n])
if (dfs(num[i + n], num[i]))
cout << "Y";
else
cout << "?";
else if (dfs(num[i], num[i + n]))
cout << "N";
else
cout << "?";
return 0;
}