题目描述
分析
对于 (20\%) 的数据,我们随便写个搜索就可以了
对于 (100\%) 的数据,建图的方式很神奇
我们从一张卡牌背面的数字向其正面的数字连边
这样问题就转化为了翻转最少的边,使得所有点的入度不超过一
为了方便处理,我们从正面向反面建一条权值为一的边,从反面向正面建一条权值为零的边
这样在计算反转次数是只要加上边权就可以了
首先我们要把图中所有的联通块预处理出来
如果一些点想要形成联通块,那么边数一定大于等于 (n-1)
而根据鸽巢原理,如果边数大于 (n),那么至少会有一个点的入度为 (2)
所以只可能有两种情况
1、联通块形成一棵树
此时树中必定有且只有一个节点的入度为 (0)
我们就可以枚举这一个入度为 (0) 的点是哪一个,然后进行树形 (DP)
直接暴力枚举是不行的,可以换根
2、联通块形成一个环
此时该基环树一定是一个外向树(不在环上的点的连边的方向指向环外)
环之外的点的方向一定是确定的
那么我们只要枚举环上的点是逆时针还是顺时针即可
代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define rg register
inline int read() {
rg int x = 0, fh = 1;
rg char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
fh = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * fh;
}
const int maxn = 2e5 + 5;
const int mod = 998244353;
int h[maxn], tot = 2;
struct asd {
int frm, to, nxt, val;
} b[maxn];
inline void ad(int aa, int bb, int cc) {
b[tot].frm = aa;
b[tot].to = bb;
b[tot].val = cc;
b[tot].nxt = h[aa];
h[aa] = tot++;
}
int t, n, ds, bs, f[maxn], g[maxn], cnt, sum, sto, rt, nowcnt, nowsum, A, B, bjg, ansA, ansB, cnthuan;
bool vis[maxn], huan[maxn], visA[maxn], visB[maxn];
void qk() {
memset(h, -1, sizeof(h));
memset(b, 0, sizeof(b));
tot = 2, sum = 1, cnt = 0, sto = 0;
memset(vis, 0, sizeof(vis));
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
memset(huan, 0, sizeof(huan));
memset(visA, 0, sizeof(visA));
memset(visB, 0, sizeof(visB));
}
void dfs(int now) {
ds++;
vis[now] = 1;
for (rg int i = h[now]; i != -1; i = b[i].nxt) {
rg int u = b[i].to;
bs++;
if (!vis[u])
dfs(u);
}
}
//找出联通块
void dfs2(int now, int fa) {
for (rg int i = h[now]; i != -1; i = b[i].nxt) {
rg int u = b[i].to;
if (u == fa)
continue;
dfs2(u, now);
g[now] += g[u] + b[i].val;
}
}
void dfs3(int now, int fa) {
if (now == rt) {
f[now] = g[now];
if (f[now] < nowcnt) {
nowcnt = f[now];
nowsum = 1;
} else if (f[now] == nowcnt) {
nowsum++;
}
}
for (rg int i = h[now]; i != -1; i = b[i].nxt) {
rg int u = b[i].to;
if (u == fa)
continue;
f[u] = f[now];
if (b[i].val == 1)
f[u]--;
else
f[u]++;
if (f[u] < nowcnt) {
nowcnt = f[u];
nowsum = 1;
} else if (f[u] == nowcnt) {
bjg = i;
nowsum++;
}
dfs3(u, now);
}
}
//换根
void findit(int now, int fa) {
huan[now] = 1;
cnthuan++;
for (rg int i = h[now]; i != -1; i = b[i].nxt) {
rg int u = b[i].to;
if (u == fa)
continue;
if (!huan[u])
findit(u, now);
else {
A = u, B = now, bjg = i;
}
}
}
//找环
void hahaA(int now) {
visA[now] = 1;
for (rg int i = h[now]; i != -1; i = b[i].nxt) {
rg int u = b[i].to;
if (visA[u] || i == bjg || i == (bjg ^ 1))
continue;
ansA += b[i].val;
hahaA(u);
}
}
//顺时针跑一遍
void hahaB(int now) {
visB[now] = 1;
for (rg int i = h[now]; i != -1; i = b[i].nxt) {
rg int u = b[i].to;
if (visB[u] || i == bjg || i == (bjg ^ 1))
continue;
ansB += b[i].val;
hahaB(u);
}
}
//逆时针跑一遍
int main() {
t = read();
rg int aa, bb;
while (t--) {
qk();
n = read();
for (rg int i = 1; i <= n; i++) {
aa = read(), bb = read();
ad(aa, bb, 1), ad(bb, aa, 0);
}
for (rg int i = 1; i <= n * 2; i++) {
if (!vis[i]) {
ds = 0, bs = 0;
dfs(i);
bs /= 2;
if (!bs)
continue;
if (bs > ds) {
sto = 1;
break;
} else if (ds == bs + 1) {
rt = i, nowcnt = 0x3f3f3f3f, nowsum = 0;
dfs2(i, 0), dfs3(i, 0);
cnt += nowcnt;
sum = 1LL * sum * nowsum % mod;
} else {
ansA = 0, ansB = 0, cnthuan = 0;
findit(i, 0);
if (cnthuan == 1) {
continue;
} else {
hahaA(A), hahaB(B);
if (b[bjg].val == 1)
ansA++;
else
ansB++;
if (ansA == ansB) {
cnt += ansA;
sum = sum * 2 % mod;
} else {
cnt += std::min(ansA, ansB);
}
}
}
}
}
if (sto) {
printf("-1 -1
");
} else {
printf("%d %d
", cnt, sum);
}
}
return 0;
}