Glorious Brilliance
Time Limit: 5000/2500 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 421 Accepted Submission(s): 71
Special Judge
Problem Description
Professor Zhang is trying to solve one of Karp's 21 NP-complete problems --
Graph Coloring Problem.
At first, he generates an undirected graph with n vertices and m edges. Then, he colors all the vertices black or white. Finally, he wants to use the following operation to make the vertices correctly colored: choose two adjacent vertices and swap their colors. The vertices are correctly colored if and only if no two adjacent vertices share the same color.
Professor Zhang wants to know the minimum number of operations needed.
At first, he generates an undirected graph with n vertices and m edges. Then, he colors all the vertices black or white. Finally, he wants to use the following operation to make the vertices correctly colored: choose two adjacent vertices and swap their colors. The vertices are correctly colored if and only if no two adjacent vertices share the same color.
Professor Zhang wants to know the minimum number of operations needed.
Input
There are multiple test cases. The first line of input contains an integer
T
, indicating the number of test cases. For each test case:
The first line contains two integers n and m (2≤n≤500,1≤m≤n(n−1)2) -- the number of vertices and the number of edges. The second line contains a binary string of length n . The i -the vertex is colored white if the i -th character is "0", or black otherwise.
In the next m lines, each contains two integers xi and yi (1≤xi,yi≤n,xi≠yi) , denoting an undirected edge.
The first line contains two integers n and m (2≤n≤500,1≤m≤n(n−1)2) -- the number of vertices and the number of edges. The second line contains a binary string of length n . The i -the vertex is colored white if the i -th character is "0", or black otherwise.
In the next m lines, each contains two integers xi and yi (1≤xi,yi≤n,xi≠yi) , denoting an undirected edge.
Output
For each test case, output an integer
s
denoting the minimum number of operations in the first line. In the next
s
lines, each contains two integers
xi
and
yi
(1≤xi,yi≤n,xi≠yi)
, denoting the
i
-th operation. If there are multiple solutions, you can output any of them.
If there's no such solution, just output "-1" in a single line.
If there's no such solution, just output "-1" in a single line.
Sample Input
3 4 4 0011 1 2 2 3 3 4 4 1 2 1 00 1 2 6 7 011001 1 4 1 5 4 2 5 2 5 3 2 6 6 3
Sample Output
1 4 1 -1 2 2 4 3 5
Author
zimpha
Source
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 505, M = 505 * 505 * 2 + 100000, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n, m;
vector<int>a[N]; //记录原始图
vector<int>p[N]; int T; //记录每个二分图中的点
int c[N]; //二分图染色标记
char s[N]; //原始球色标记
//显然,这个图必然要求为二分图,否则一定非法;合法情况下我们存入该二分图的边和点
bool dfs(int x, int col)
{
c[x] = col;
p[T].push_back(x);
for (int i = a[x].size() - 1; ~i; --i)
{
int y = a[x][i];
if (c[y])
{
if (c[y] == c[x])return 0;
}
else if (!dfs(y, 3 - c[x]))return 0;
}
return 1;
}
int id, ST, ED;
int first[N];
int w[M], cap[M], cost[M], nxt[M];
int f[N];//f[x]表示x距离ST的有流量的边的最小距离
int pe[N];//pe[x]记录流向x的前驱边
bool e[N];//e[x]是判定点x是否在spfa队列中的
void ins(int x, int y, int cap_, int cost_)
{
w[++id] = y;
cap[id] = cap_;
cost[id] = cost_;
nxt[id] = first[x];
first[x] = id;
w[++id] = x;
cap[id] = 0;
cost[id] = -cost_;
nxt[id] = first[y];
first[y] = id;
}
queue<int>q;
void inq(int x, int cost_, int pe_)
{
if (cost_ >= f[x])return;//单位流量费用更大,没有更新意义
f[x] = cost_;//单位流量费用小的条件下做更新
pe[x] = pe_;//然后记录上一个点和上一条边
if (e[x])return; e[x] = 1;//入队标记,防止重复入队做冗余更新
q.push(x);
}
bool spfa()
{
MS(f, 63);
cap[0] = ms63;
inq(ST, 0, 0);
while (!q.empty())
{
int x = q.front(); q.pop(); e[x] = 0;
for (int z = first[x]; z; z = nxt[z])
{
if (cap[z])inq(w[z], f[x] + cost[z], z);
}
}
return f[ED]<ms63;
}
int maxflow, mincost;
void MCMF()
{
maxflow = 0;
mincost = 0;
while (spfa())
{
//从终点滚到起点,确定此费用下的最大流量,并修改残余流量
int flow = ms63;
int x = ED;
while (x != ST)
{
gmin(flow, cap[pe[x]]);
x = w[pe[x] ^ 1];
}
maxflow += flow;
mincost += f[ED] * flow;
x = ED;
while (x != ST)
{
cap[pe[x]] -= flow;
cap[pe[x] ^ 1] += flow;
x = w[pe[x] ^ 1];
}
}
}
int build(int t, int col, char ch)
{
MS(first, 0); id = 1;
ST = 0; ED = n + 1;
int num1 = 0;
int num2 = 0;
for (int i = p[t].size() - 1; ~i; --i)
{
int x = p[t][i];
if (c[x] == col && s[x] == ch)
{
ins(ST, x, 1, 0);
++num1;
}
if (c[x] != col && s[x] != ch)
{
ins(x, ED, 1, 0);
++num2;
}
for (int j = a[x].size() - 1; ~j; --j)
{
int y = a[x][j];
ins(x, y, ms63, 1);
}
}
if (num1 != num2)return 1e9;
MCMF();
if (maxflow != num1)return 1e9;
return mincost;
}
struct Ans
{
int x, y; char ch;
};
vector<Ans>now, ans;
bool transform(int t)
{
int co = 1e9;
int tmp;
if ((tmp = build(t, 1, '0')) < co)
{
now.clear(); co = tmp;
for (int i = 3; i <= id; i += 2)if (cost[i] == -1 && cap[i])
{
for (int j = 1; j <= cap[i]; ++j) now.push_back({ w[i], w[i ^ 1],'0' });
}
}
if ((tmp = build(t, 1, '1')) < co)
{
now.clear(); co = tmp;
for (int i = 3; i <= id; i += 2)if (cost[i] == -1 && cap[i])
{
for (int j = 1; j <= cap[i]; ++j) now.push_back({ w[i], w[i ^ 1],'1' });
}
}
if (co < 1e9)
{
for (int i = now.size() - 1; ~i; --i)ans.push_back(now[i]);
return 1;
}
else return 0;
}
bool use[N*N];
void print()
{
int g = ans.size(); for (int i = 0; i < g; ++i)use[i] = 0;
printf("%d\n", g);
bool flag = 1;
while (flag)
{
flag = 0;
for (int i = 0; i < g; ++i)if (!use[i])
{
int x = ans[i].x; int y = ans[i].y;
if (s[x] != s[y] && s[x] == ans[i].ch)
{
printf("%d %d\n", x, y);
swap(s[x], s[y]);
use[i] = 1;
flag = 1;
}
}
}
}
void init()
{
//二分图染色状况初始化
for (int i = 1; i <= n; ++i)
{
a[i].clear();
c[i] = 0;
}
//二分图点边存放状况初始化
for (int j = 1; j <= T; ++j)p[j].clear();
T = 0;
}
bool check()
{
//先判定是否为二分图
for (int i = 1; i <= n; ++i)if (!c[i])
{
++T;
if (!dfs(i, 1))return 0;
}
//在确定为二分图的基础上尝试完成交换
ans.clear();
for (int i = 1; i <= T; ++i)
{
if (!transform(i))return 0;
}
return 1;
}
int main()
{
scanf("%d", &casenum);
for (casei = 1; casei <= casenum; ++casei)
{
scanf("%d%d", &n, &m);
init();
scanf("%s", s + 1);
for (int i = 1; i <= m; ++i)
{
int x, y;
scanf("%d%d", &x, &y);
a[x].push_back(y);
a[y].push_back(x);
}
if (check())print();
else puts("-1");
}
return 0;
}
/*
【trick&&吐槽】
被提前验过题的一队误导,开场就做这题。
然后一眼就感觉会做了,想拿一血。
最后跪在了路径输出那里。
造成了这场比赛的大崩盘。
这道题的方案输出是个难点,因为交换是应当有顺序的。
【题意】
给你一个图,图上n(500)个点,最多C(n,2)条无向边。
每个点初始都有一个数字0或1,
我们想使得,在相邻数字之间交换数字,使得最终相邻点之间的数字都不同。
并且在这个基础上,使得交换次数最小。
输出最小交换次数和具体的交换方案(有顺序)
【类型】
二分图染色+费用流
【分析】
这道题目,首先必然要求原图为一个二分图,否则必然无法实现染色。
在原图是二分图的条件下,
我们给每个点分配了一个颜色。
对于一个联通二分图,我们可能使得某个颜色的点,数字都为0,或者数字都为1。
于是我们要枚举,使得某个颜色的点,为数字0或者数字1.
交换的最小费用是多少呢?我们可以通过费用流实现。
1,我们假定颜色1为都为数字0,颜色2都为数字1。
于是以颜色1数字1的点为源点出点,以颜色2数字0的点为汇点入点,跑最小费用最大流
2,我们假定颜色1为都为数字1,颜色2都为数字0。
于是以颜色1数字0的点为源点出点,以颜色2数字1的点为汇点入点,跑最小费用最大流
显然,出点和入点的数量一定要相等。最大流量一定要=出点数=入点数。
对于1 2 两种建图方式,在保证最大流量满足要求的情况下(即有解条件下),
我们选取最小费用者为答案。
这样子我们处理到最后,是可以判定合法性并且得到最小费用了。
然而,我们还有最大的一个问题,如何输出路径。
我们可以知道从源点到汇点,每条边交换了几次,但是具体的顺序怎么搞,却难以安排。
比赛的时候想,我们在知道每条边交换几次的情况下,
在其交换次数范围内,我们看这条边两侧是否为一0一1,满足一0一1则交换。
不过这种方法是错误的。
举例说,一条边初始就是一0一1,这条边我们可能交换多次(把这条边一侧的多个点向另一侧转移)
对于我的出解方案,就可能连续输出这条边,使得交换来又交换回去,出现了重复交换,造成了GG.
最终比赛的时候没有过。
然而,其实只要加一个条件就可以过了T____T
我们在求出最大流的时候,确定每次条边交换了几次,还要确定这条边在的一端,在是'0'还是'1'的条件下做交换。
最后我们输出方案的时候,暴力枚举所有边,在满足给定一侧的点,满足交换条件的时候做交换。
这样就会使得我们每次交换,都是有意义的交换,最终就AC了。
真是好可惜啊!
【时间复杂度&&优化】
O(nm)
*/