题目
思路
图论模型
一个合法序列的最后一个方块,只由倒数第二个方块所限制。
这样的限制,往往可以建立图论模型。
注意到一个方块的本质是让结尾颜色变化一次。将一个方块两边的颜色 ⟨ a , b ⟩ \langle a,b\rangle ⟨a,b⟩ 视作一条连接 a , b a,b a,b 的双向边。则答案为原图中边不相交的最长路径。
挑战 N P − H a r d \tt{NP-Hard} NP−Hard
边不相交?这似乎是 欧拉路径 的必要条件呢!
如果我把走过的边抽象出来,这一定是一条欧拉路径(小知识:其充要条件是奇点数量不超过二)。
于是,我们力求找到原图的边集的子集 E ′ ( E ′ ⊆ E ) E'(E'\subseteq E) E′(E′⊆E) ,满足 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;
}