BZOJ2887 : 旅行
如果小地图存在欧拉回路,那么对于大地图的每条边,都可以恰好走两次使得小地图每条边恰好经过一次,令$sum$为小地图的边权和,则此时答案为$m\times sum$。
否则小地图存在欧拉路径,找到两个奇点$A$和$B$,那么最优解中大地图每条边一定经过$1$次或者$2$次,且仅保留经过$1$次的边后大地图里每个点度数都是$0$。
令$d[i][j]$为小地图中$i$到$j$的最短路,若要从$i$到$j$走一次,那么最优方案是把$i,j,A,B$这$4$个点用最短路配成两对,牺牲掉这两条最短路;若要从$i$到$j$走两次,那么最优方案牺牲掉$A,B$的最短路。
注意到$m\leq n+5$,所以取大地图的一棵生成树,然后$2^{m-n+1}$暴力枚举非树边的经过次数,那么每条树边的经过次数可以从下而上递推得到。
时间复杂度$O(p^3+n2^{n-m+1})$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=10010,M=205; const ll inf=1LL<<60; int n,m,ce,P,Q,A,B,i,j,k,x,y,z,S,at[N],f[N],e[N][2],deg[N];ll d[M][M],sum,ans,base; int g[N],v[N<<1],nxt[N<<1],ed,q[N],cnt;ll w1[N],w2[N]; int F(int x){return f[x]==x?x:f[x]=F(f[x]);} inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} inline void up(ll&a,ll b){a>b?(a=b):0;} inline ll ask1(int x,int y){ x=at[x],y=at[y]; return max(max(sum-d[A][x]-d[B][y],sum-d[A][y]-d[B][x]),sum-d[x][y]-d[A][B]); } inline ll ask2(int x,int y){return sum-d[A][B];} void dfs(int x,int y){ f[q[++cnt]=x]=y; if(y)w1[x]=ask1(x,y),w2[x]=ask2(x,y); for(int i=g[x];i;i=nxt[i])if(v[i]!=y)dfs(v[i],x); } int main(){ scanf("%d%d%d%d",&n,&m,&P,&Q); for(i=1;i<=n;i++)scanf("%d",&at[i]); for(i=1;i<=n;i++)f[i]=i; for(i=1;i<=m;i++){ scanf("%d%d",&x,&y); if(F(x)!=F(y))f[f[x]]=f[y],add(x,y),add(y,x); else{ e[ce][0]=x; e[ce++][1]=y; } } for(i=1;i<=P;i++)for(j=1;j<=P;j++)d[i][j]=i==j?0:inf; while(Q--){ scanf("%d%d%d",&x,&y,&z); deg[x]^=1,deg[y]^=1,sum+=z; d[x][y]=d[y][x]=z; } for(i=1;i<=P;i++)if(deg[i]){ if(!A)A=i; else B=i; } if(!A)return printf("%lld",sum*m),0; for(k=1;k<=P;k++)for(i=1;i<=P;i++)for(j=1;j<=P;j++)up(d[i][j],d[i][k]+d[k][j]); dfs(1,0); for(S=0;S<1<<ce;S++){ base=0; for(i=1;i<=n;i++)deg[i]=0; for(i=0;i<ce;i++){ x=e[i][0],y=e[i][1]; if(S>>i&1)deg[x]^=1,deg[y]^=1,base+=ask1(x,y);else base+=ask2(x,y); } for(i=n;i>1;i--){ x=q[i]; if(deg[x])deg[f[x]]^=1,base+=w1[x];else base+=w2[x]; } ans=max(ans,base); } return printf("%lld",ans),0; }