同桌的你(环套树)(DP)
同桌的你
题目大意
给你一个 n 个点 m 个边的图,每个点一定会至少有一条边。
然后要你选尽可能多的点对,使得每个点对之间都有边。
然后每个点有 1/2 的点权,你在选点对最多的基础上,还要让点权不同的点对尽可能多。
然后要输出其中一种方案。
思路
首先你考虑看到这个图应该是一些连通块,每个连通块只会有一个环。
那就是一个环套树森林。
那你考虑分别解决每个森林。
然后你发现因为要最多点对所以在环上的相邻两个点如果选了不优一定是因为有一个点跟其他匹配了。
那你考虑找环上相邻的两条边,分别断掉,然后做 DP,然后选最优的那个作为解。
然后讲一下如何 DP,设 \(f_{u,0/1}\) 为搞定 \(i\) 子树,\(i\) 不配对或者配对的最大分数。
然后 \(f_{u,0}=\sum\limits_{v=son_u}\max\{f_{v,0},f_{v,1}\}\)
然后 \(f_{u,1}=\sum\limits_{v=son_u}\max\{f_{u,0}- \max\{f_{v,0},f_{v,1}\}+f_{u,0}+val_{u,v}\}\)
然后两个权值搞一下即可。
(不过有一种玩法就是线段树加很大边权的思路,你把首要条件弄成 \(1e9\),次要弄成 \(1\),然后求出来的答案 \(/1e9\) 就是首要条件的答案,\(\bmod\ 1e9\) 就是次要条件的答案)
然后至于计算方案你就记录一下是从哪里转移过来的就行了。
然后要卡常。
代码
dfs 版(在可恶的会爆栈空间的 jzoj 上只能有 50 分)
#include<queue>//这里没有卡常,所以就算开了栈空间也可能会 TLE
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
struct node {
ll x;
int to, nxt, rgt;
}e[2000001];
int T, n, a[1000001], b[1000001], kill1, kill2;
int fa[1000001], dfn[1000001], tmp;
int low[1000001], in[1000001], remm;
int sta[1000001], le[1000001], KK, st;
int spc[5], totans, totjh, rem[2][1000001][2];
vector <int> jh[1000001], ansbh;
ll f[2][1000001][2];
queue <int> q, qq, qqq;
void add(int x, int y, ll z) {
e[++KK] = (node){z, y, le[x], 1}; le[x] = KK;
e[++KK] = (node){z, x, le[y], 0}; le[y] = KK;
}
int find(int now) {
if (fa[now] == now) return now;
return fa[now] = find(fa[now]);
}
void connect(int x, int y) {
int X = find(x), Y = find(y);
if (X == Y) return ;
fa[X] = Y;
}
int DP(int now, int op, int father) {
st = 0;
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
if (i == kill1 || i == kill2) continue;
// if (now == spc[op + 1] && e[i].to == spc[op]) continue;
DP(e[i].to, op, now);
f[op][now][0] += max(f[op][e[i].to][1], f[op][e[i].to][0]);//先搞不选它的
}
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
if (i == kill1 || i == kill2) continue;
// if (now == spc[op + 1] && e[i].to == spc[op]) continue;
if (f[op][now][1] < f[op][now][0] - max(f[op][e[i].to][1], f[op][e[i].to][0]) + f[op][e[i].to][0] + e[i].x) {
f[op][now][1] = f[op][now][0] - max(f[op][e[i].to][1], f[op][e[i].to][0]) + f[op][e[i].to][0] + e[i].x;
rem[op][now][1] = e[i].to;
}//然后选一个儿子跟它配对
}
}
void work() {
for (int i = le[spc[1]]; i; i = e[i].nxt)
if (e[i].rgt && e[i].to == spc[0]) {
kill1 = i; kill2 = i + 1; break;
}
DP(spc[0], 0, 0);
for (int i = le[spc[2]]; i; i = e[i].nxt)
if (e[i].rgt && e[i].to == spc[1]) {
kill1 = i; kill2 = i + 1; break;
}
DP(spc[0], 1, 0);
ll x, y, xx, yy;
if (f[0][spc[0]][0] > f[0][spc[0]][1]) x = f[0][spc[0]][0], xx = 0;
else x = f[0][spc[0]][1], xx = 1;
if (f[1][spc[0]][0] > f[1][spc[0]][1]) y = f[1][spc[0]][0], yy = 0;
else y = f[1][spc[0]][1], yy = 1;
if (x > y) {//两次 DP 的结果哪个优用哪个
int op = 0;
totans += x / 1000000000;
totjh += x % 1000000000;
while (!q.empty()) q.pop();
while (!qq.empty()) qq.pop();
while (!qqq.empty()) qqq.pop();
q.push(spc[0]); qq.push(xx); qqq.push(0);
while (!q.empty()) {
for (int i = le[spc[1]]; i; i = e[i].nxt)
if (e[i].rgt && e[i].to == spc[0]) {
kill1 = i; kill2 = i + 1; break;
}
int now = q.front(); q.pop();
xx = qq.front(); qq.pop();
int fafa = qqq.front(); qqq.pop();
for (int i = le[now]; i; i = e[i].nxt)//找方案
if (e[i].to != fafa) {
if (i == kill1 || i == kill2) continue;
// if (now == spc[op + 1] && e[i].to == spc[op]) continue;
if ((xx && e[i].to == rem[op][now][xx]) || f[op][e[i].to][1] < f[op][e[i].to][0]) {
q.push(e[i].to); qq.push(0); qqq.push(now);
}
else {
if (f[op][e[i].to][1]) {
q.push(e[i].to); qq.push(1); qqq.push(now);
}
}
if (xx && e[i].to == rem[op][now][xx]) {
ansbh.push_back(now); ansbh.push_back(e[i].to);
}
}
}
}
else {
for (int i = le[spc[2]]; i; i = e[i].nxt)
if (e[i].rgt && e[i].to == spc[1]) {
kill1 = i; kill2 = i + 1; break;
}
int op = 1;
totans += y / 1000000000;
totjh += y % 1000000000;
while (!q.empty()) q.pop();
q.push(spc[0]); q.push(yy); q.push(0);
while (!q.empty()) {
int now = q.front(); q.pop();
xx = q.front(); q.pop();
int fafa = q.front(); q.pop();
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != fafa) {
if (i == kill1 || i == kill2) continue;
// if (now == spc[op + 1] && e[i].to == spc[op] && e[i].rgt) continue;
if ((xx && e[i].to == rem[op][now][xx]) || f[op][e[i].to][1] < f[op][e[i].to][0]) {
q.push(e[i].to); q.push(0); q.push(now);
}
else {
q.push(e[i].to); q.push(1); q.push(now);
}
if (xx && e[i].to == rem[op][now][xx]) {
ansbh.push_back(now); ansbh.push_back(e[i].to);
}
}
}
}
}
void tarjan(int now) {
dfn[now] = low[now] = ++tmp;
sta[++sta[0]] = now;
for (int i = le[now]; i; i = e[i].nxt) {
if (!e[i].rgt) continue;
if (!dfn[e[i].to]) {
tarjan(e[i].to); low[now] = min(low[now], low[e[i].to]);
}
else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
}
if (dfn[now] == low[now]) {
in[now] = find(now);
int temp = 0;
while (sta[sta[0]] != now) {
in[sta[sta[0]]] = find(now);
if (temp < 3) {
spc[temp] = sta[sta[0]];
temp++;
}
sta[0]--;
}
if (temp < 3) {
spc[temp] = sta[sta[0]];
temp++;
}
sta[0]--;
if (temp >= 3) {
work();
}
else if (temp == 2) {
spc[temp] = spc[0];
temp++;
work();
}
}
}
int main() {
// freopen("read.txt", "r", stdin);
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
tmp = 0; sta[0] = 0; KK = 0; totans = 0; totjh = 0; ansbh.clear();
for (int i = 1; i <= n; i++)
fa[i] = i, in[i] = 0, dfn[i] = 0, le[i] = 0, jh[i].clear();
memset(f, 0, sizeof(f)); memset(rem, 0, sizeof(rem));
for (int i = 1; i <= n; i++) {
scanf("%d %d", &a[i], &b[i]);
connect(a[i], i);
}
for (int i = 1; i <= n; i++) {
add(i, a[i], 1000000000 + (b[i] != b[a[i]]));
}
for (int i = 1; i <= n; i++) jh[find(i)].push_back(i);
for (int i = 1; i <= n; i++)
if (!dfn[i]) {
tarjan(i);
}
printf("%d %d\n", totans, totjh);
for (int i = 0; i < totans; i++) {
printf("%d %d\n", ansbh[i * 2], ansbh[i * 2 + 1]);
}
}
return 0;
}
bfs 版(找环不再用 tarjan,而是直接跳找)
#pragma GCC optimize(2)//加油吧!卡常人!
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#define ll long long
#define rr register
using namespace std;
struct node {
ll x;
int to, nxt, rgt;
}e[2000001];
int T, n, a[1000001], b[1000001], kill1, kill2;
int dfn[1000001], tmp, xx, yy, temp, kill3, kill4;
int le[1000001], KK, st;
int spc[5], totans, totjh, rem[2][1000001];
int stac[1000001], Fa[1000001], re;
vector <int> ansbh;
ll f[2][1000001][2], x, y;
queue <int> q;
char c;
void add(int x, int y, ll z) {
e[++KK] = (node){z, y, le[x], 1}; le[x] = KK;
e[++KK] = (node){z, x, le[y], 0}; le[y] = KK;
}
int DP(int now, int op, int father) {
stac[0] = 0;
stac[++stac[0]] = now;
Fa[stac[0]] = father;
for (rr int i = 1; i <= stac[0]; i++) {
now = stac[i]; father = Fa[i];
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
if (i == kill1 || i == kill2) continue;
stac[++stac[0]] = e[i].to;
Fa[stac[0]] = now;
}
}
for (rr int i = stac[0]; i >= 1; i--) {
now = stac[i]; father = Fa[i];
st = 0;
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
if (i == kill1 || i == kill2) continue;
f[op][now][0] += max(f[op][e[i].to][1], f[op][e[i].to][0]);
}
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
if (i == kill1 || i == kill2) continue;
if (f[op][now][1] < f[op][now][0] - max(f[op][e[i].to][1], f[op][e[i].to][0]) + f[op][e[i].to][0] + e[i].x) {
f[op][now][1] = f[op][now][0] - max(f[op][e[i].to][1], f[op][e[i].to][0]) + f[op][e[i].to][0] + e[i].x;
rem[op][now] = e[i].to;
}
}
}
}
ll xxx, yyy;
int xxxx, yyyy, op, fafa;
void work() {
int now;
for (int i = le[spc[0]]; i; i = e[i].nxt)
if (e[i].rgt && e[i].to == spc[1]) {
kill1 = i; kill2 = i + 1; break;
}
for (int i = le[spc[1]]; i; i = e[i].nxt)
if (e[i].rgt && e[i].to == spc[2]) {
kill3 = i; kill4 = i + 1; break;
}
DP(spc[0], 0, 0);
swap(kill1, kill3); swap(kill2, kill4);
DP(spc[0], 1, 0);
if (f[0][spc[0]][0] > f[0][spc[0]][1]) xxx = f[0][spc[0]][0], xxxx = 0;
else xxx = f[0][spc[0]][1], xxxx = 1;
if (f[1][spc[0]][0] > f[1][spc[0]][1]) yyy = f[1][spc[0]][0], yyyy = 0;
else yyy = f[1][spc[0]][1], yyyy = 1;
if (xxx > yyy) {
op = 0;
totans += xxx / 1000000000;
totjh += xxx % 1000000000;
swap(kill1, kill3); swap(kill2, kill4);
q.push(spc[0]); q.push(xxxx); q.push(0);
while (!q.empty()) {
int now = q.front(); q.pop();
xxxx = q.front(); q.pop();
int fafa = q.front(); q.pop();
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != fafa) {
if (i == kill1 || i == kill2) continue;
if ((xxxx && e[i].to == rem[op][now]) || f[op][e[i].to][1] < f[op][e[i].to][0]) {
q.push(e[i].to); q.push(0); q.push(now);
}
else {
if (f[op][e[i].to][1]) {
q.push(e[i].to); q.push(1); q.push(now);
}
}
if (xxxx && e[i].to == rem[op][now]) {
ansbh.push_back(now); ansbh.push_back(e[i].to);
}
}
}
}
else {
op = 1;
totans += yyy / 1000000000;
totjh += yyy % 1000000000;
q.push(spc[0]); q.push(yyyy); q.push(0);
while (!q.empty()) {
now = q.front(); q.pop();
yyyy = q.front(); q.pop();
fafa = q.front(); q.pop();
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != fafa) {
if (i == kill1 || i == kill2) continue;
if ((yyyy && e[i].to == rem[op][now]) || f[op][e[i].to][1] < f[op][e[i].to][0]) {
q.push(e[i].to); q.push(0); q.push(now);
}
else {
q.push(e[i].to); q.push(1); q.push(now);
}
if (yyyy && e[i].to == rem[op][now]) {
ansbh.push_back(now); ansbh.push_back(e[i].to);
}
}
}
}
}
void get_cir(int now) {
y = now; tmp++;
for (y; !dfn[y]; y = a[y]) {
dfn[y] = tmp;
}
temp = 0;
spc[0] = y; temp++;
spc[1] = a[y]; temp++;
if (a[a[y]] != y) spc[2] = a[a[y]], temp++;
if (temp >= 3) {
work();
}
else if (temp == 2) {
spc[temp] = spc[0];
temp++;
work();
}
stac[0] = 0;
stac[++stac[0]] = now;
for (rr int ttt = 1; ttt <= stac[0]; ttt++) {
int now = stac[ttt];
for (int i = le[now]; i; i = e[i].nxt)
if (dfn[e[i].to] != -1) {
dfn[e[i].to] = -1;
stac[++stac[0]] = e[i].to;
}
}
}
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
re = 0; c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = re * 10 + c - '0';
c = getchar();
}
return re;
}
void write(int x) {
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
int main() {
// freopen("read.txt", "r", stdin);
// freopen("write.txt", "w", stdout);
T = read();
while (T--) {
n = read();
tmp = 0; KK = 0; totans = 0; totjh = 0; ansbh.clear();
for (rr int i = 1; i <= n; i++) {
dfn[i] = 0, le[i] = 0;
f[0][i][0] = f[0][i][1] = f[1][i][0] = f[1][i][1] = 0;
rem[0][i] = rem[1][i] = 0;
}
for (rr int i = 1; i <= n; i++) {
a[i] = read(); b[i] = read();
}
for (rr int i = 1; i <= n; i++) {
add(i, a[i], 1000000000 + (b[i] != b[a[i]]));
}
for (rr int i = 1; i <= n; i++)
if (dfn[i] != -1) {
get_cir(i);
}
write(totans); putchar(' '); write(totjh); putchar('\n');
for (rr int i = 0; i < totans; i++) {
write(ansbh[i << 1]); putchar(' '); write(ansbh[i << 1 | 1]); putchar('\n');
}
}
return 0;
}