2021银川Problem D. Farm
(注:由于没有数据,暂不保证正确性)
题意:
有n个点,m个有权边,有q个限制条件,每个限制条件有两个选择:选u个边,选第v个边,两个选择至少要选一个。
问联通所有边的最小花费是多少?
0<=q<=16
1<=n<=1e5
1<=m<=5e5
题解:
不难看出q很小,因此我们可以用二进制的方法直接枚举直接枚举每个选择的情况。比如第i位是0表示第i个选择选的是边u,否则选的边v
这样复杂度是O(216ElogE)O(2^{16}ElogE)O(216ElogE),会超时
此时我们需要将E降低
我们想下,是否存在一些边是必选的,也就是不会受q的影响
限制的边指的是被q选中的边
我们将所有边进行排序,第一顺序为限制的边在前,非限制的边在后,第二顺序是费用从小到大
然后跑一边krusal,然后选到了n-1个边(最小生成树的边)
我们设n-1个边中限制边的数量为x(0<=x<=32),那么非限制边就是y=n-1-x(n-1<=y<=n-33)。也就是说无论限制边如何选择,有y个非限制边是一定会选择的,那我们就将这个y个边加入到最小生成树中,记录这y个边权值。这样就从原先n个点变成x+1个点未加入到最小生成树中
虽然只剩下x+1个点,但是任意两个点之间可能有很多边,我们可以将其减少,使得每两个点之间只有一个边
我们将所有边按照之前的排序方式再排,然后再跑一边最小生成树,将限定边和可以加入最小生成树的边存下来,这样得到新的边集,数量为tot
此时的tot最大为64,完全可以跑过
详细看代码(注:不保证正确)
代码:
#include <bits/stdc++.h>
#include <unordered_map>
#define debug( a, b ) printf ( "%s = %d\n", a, b );
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
clock_t startTime, endTime;
// Fe~Jozky
const ll INF_ll = 1e18;
const int INF_int = 0x3f3f3f3f;
void read (){};
template <typename _Tp, typename... _Tps> void read ( _Tp &x, _Tps &...Ar )
{x = 0;char c = getchar ();bool flag = 0;while ( c < '0' || c > '9' )flag |= ( c == '-' ), c = getchar ();while ( c >= '0' && c <= '9' )x = ( x << 3 ) + ( x << 1 ) + ( c ^ 48 ), c = getchar ();if ( flag )x = -x;read ( Ar... );
}
template <typename T> inline void write ( T x )
{if ( x < 0 ){x = ~( x - 1 );putchar ( '-' );}if ( x > 9 )write ( x / 10 );putchar ( x % 10 + '0' );
}
void rd_test ()
{#ifdef ONLINE_JUDGE
#elsestartTime = clock ();freopen ( "in.txt", "r", stdin );
#endif
}
void Time_test ()
{#ifdef ONLINE_JUDGE
#elseendTime = clock ();printf ( "\nRun Time:%lfs\n",(double)( endTime - startTime ) / CLOCKS_PER_SEC );
#endif
}
const int MAXM = 500005;struct Edge
{int a, b, c, id;
} edge[MAXM], tmp[MAXM];
;int fa[MAXM];
void init ( int m, Edge *edge )
{for ( int i = 1; i <= m; i++ ){fa[edge[i].a] = edge[i].a;fa[edge[i].b] = edge[i].b;}
}
int find ( int x )
{return fa[x] == x ? x : fa[x] = find ( fa[x] );
}
bool merge ( int x, int y )
{x = find ( x ), y = find ( y );if ( x == y )return 0;fa[x] = y;return 1;
}int u[50], v[50];
bool key[MAXM], usd[MAXM];
/*
排序,第一顺序为限制,非限制,第二顺序为费用从小到大
跑一边krusal得到n-1个边
此时n-1个边中的非限制边是一定要选的
其中非限制边的数量为x,n-1-32<=x<=n-1
我们将这n-1个边中的非限制便直接选上然后将还可以加入最小生成树的边或者是限定选选出来,这是我们需要考虑的对这些边排序,第一顺序是非限制>限制,第二顺序边权<边权*/
bool cmp ( Edge x, Edge y )
{return key[x.id] == key[y.id] ? x.c < y.c : key[x.id] > key[y.id];
}
bool cmp1 ( Edge x, Edge y )
{return x.c < y.c;
}
int contraction ( int &n, int m, int &res )
{sort ( edge + 1, edge + m + 1, cmp );init ( m, edge );int tot = 0;for ( int i = 1; i <= m; i++ )if ( merge ( edge[i].a, edge[i].b ) )tmp[++tot] = edge[i];init ( tot, tmp );for ( int i = 1; i <= tot; i++ )if ( !key[tmp[i].id] ){merge ( tmp[i].a, tmp[i].b );n--;res += tmp[i].c;}tot = 0;for ( int i = 1; i <= m; i++ ){edge[i].a = find ( edge[i].a );edge[i].b = find ( edge[i].b );if ( key[edge[i].id] || edge[i].a != edge[i].b )edge[++tot] = edge[i];}return tot;
}
int removal ( int m ) //去掉重边
{sort ( edge + 1, edge + m + 1, cmp );init ( m, edge );int tot = 0;for ( int i = 1; i <= m; i++ )if ( key[edge[i].id] || merge ( edge[i].a, edge[i].b ) )edge[++tot] = edge[i];return tot;
}
int main ()
{rd_test();int n, m;scanf ( "%d%d", &n, &m );for ( int i = 1; i <= m; i++ ){int a, b, c;scanf ( "%d%d%d", &a, &b, &c );edge[i] = Edge{ a, b, c, i };}int q;scanf ( "%d", &q );for ( int i = 0; i < q; i++ ){scanf ( "%d%d", &u[i], &v[i] );key[u[i]] = key[v[i]] = 1;}int base = 0, res = INF_int;int tmp = contraction ( n, m, base );printf("现在的边有%d条\n",tmp);m = removal ( tmp );printf("现在的边有%d条\n",m);sort ( edge + 1, edge + m + 1, cmp1 );for ( int state = 0; state < ( 1 << q ); state++ ){for ( int i = 0; i < q; i++ )usd[u[i]] = usd[v[i]] = 0;for ( int i = 0; i < q; i++ ){if ( state >> i & 1 )usd[u[i]] = 1;elseusd[v[i]] = 1;}init ( m, edge );int now = base, cnt = n;for ( int i = 1; i <= m; i++ )if ( usd[edge[i].id] ){if ( merge ( edge[i].a, edge[i].b ) )cnt--;now += edge[i].c;usd[edge[i].id] = 2;}for ( int i = 1; i <= m; i++ )if ( !usd[edge[i].id] && merge ( edge[i].a, edge[i].b ) )now += edge[i].c, cnt--;if ( cnt == 1 )res = min ( res, now );}printf ( "%d\n", ( res < INF_int ? res : -1 ) );return 0;
}