[CF1038E]Maximum Matching

122 篇文章 0 订阅

题目

传送门 to CF

传送门 to luogu

传送门 to VJ

思路

图论模型

一个合法序列的最后一个方块,只由倒数第二个方块所限制。

这样的限制,往往可以建立图论模型。

注意到一个方块的本质是让结尾颜色变化一次。将一个方块两边的颜色 ⟨ a , b ⟩ \langle a,b\rangle a,b 视作一条连接 a , b a,b a,b 的双向边。则答案为原图中边不相交的最长路径。

挑战 N P − H a r d \tt{NP-Hard} NPHard

边不相交?这似乎是 欧拉路径 的必要条件呢!

如果我把走过的边抽象出来,这一定是一条欧拉路径(小知识:其充要条件是奇点数量不超过二)。

于是,我们力求找到原图的边集的子集 E ′ ( E ′ ⊆ E ) E'(E'\subseteq E) E(EE) ,满足 G ′ = ( V , E ′ ) G'=(V,E') G=(V,E) 存在欧拉路径。

这难吗?不难。如果 ⟨ a , b ⟩ \langle a,b\rangle a,b 之间有两条边不属于 E ′ E' E ,则将这两条边加入,仍然存在欧拉路径(因为 a , b a,b a,b 的度数的奇偶性都没有变化)。

那么,我们知道了这一点: ⟨ a , b ⟩ \langle a,b\rangle a,b 之间最多只有一条边没有被经过(或者是全部都没有被经过)。

所以大力枚举吧!用 2 6 2^{6} 26 来枚举,哪几种边是没有被全部经过的。之所以指数是六,是因为 这是我算出来的 删掉的边不会是自环,于是只有六种不同的边。

然后检测,并计算答案。战斗就结束了。有意思的是, O ( n ) \mathcal O(n) O(n) 只来自于读入!

——言外之意:我允许 n n n 达到 1 0 6 10^6 106 级别。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}
# define MB template < class T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }

const int MaxN = 105, infty = (1<<28)-1;
int sum[4][4], minV[4][4], cnt[4][4];

# define FOR(o) for(int o=0; o<4; ++o)

void input(){
	int n = readint();
	FOR(i) FOR(j) minV[i][j] = infty;
	for(int i=1,l,v,r; i<=n; ++i){
		l = readint()-1;
		v = readint(); // 颜色设置为[0,4)
		r = readint()-1;
		if(l > r) swap(l,r); // 小->大
		sum[l][r] += v, ++ cnt[l][r];
		getMin(minV[l][r],v);
	}
	FOR(i) for(int j=0; j<i; ++j){
		sum[i][j] = sum[j][i];
		minV[i][j] = minV[j][i];
		cnt[i][j] = cnt[j][i];
	} // 大->小
}

bool vis[4], deg[4]; int odd;
int dfs(int x){ // 返回答案的两倍(一条边会被加两次)
	if(vis[x] == true) return 0;
	vis[x] = true; int res = sum[x][x];
	FOR(i) if(x != i and cnt[x][i] > 0){
		res += sum[x][i]+dfs(i);
		if(cnt[x][i]%2 == 1)
			deg[x] = not deg[x];
	}
	if(deg[x]) ++ odd; return res+sum[x][x];
}

int HASH(int l,int r){
	return r+l-(l==0);
} // 请自行验证,顺序为(0,1)(0,2)(0,3)(1,2)(1,3)(2,3)
void solve(){
	int ans = 0;
	for(int S=(1<<6)-1; ~S; --S){
		bool ok = true;
		FOR(i) for(int j=i+1; j<4; ++j)
			if(cnt[i][j] == 0 and (S>>HASH(i,j)&1)){
				ok = false; break;
			} // 删掉了不存在的边 
		if(not ok) continue;
		FOR(i) vis[i] = deg[i] = false;

		FOR(i) for(int j=i+1; j<4; ++j)
			if((S>>HASH(i,j)&1) != 0){
				-- cnt[i][j], -- cnt[j][i];
				sum[i][j] -= minV[i][j];
				sum[j][i] -= minV[j][i];
			} // 对图进行删边操作

		FOR(i) if(not vis[i]){
			odd = 0; int d = dfs(i)>>1;
			if(odd < 3) getMax(ans,d);
		}
		
		FOR(i) for(int j=i+1; j<4; ++j)
			if((S>>HASH(i,j)&1) != 0){
				++ cnt[i][j], ++ cnt[j][i];
				sum[i][j] += minV[i][j];
				sum[j][i] += minV[j][i];
			}/* 将图还原 */
	}
	printf("%d\n",ans);
}

int main(){
	input(), solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值