Description
Solution
算法一
首先考虑只有一种频道的情况。
注意到最终形成的是一棵树,并且 k k k 较小,这些启发我们做状压 dp \text{dp} dp。
令 f i , s f_{i,s} fi,s 表示,目前树的根为 i i i,且树内包含了情报站集合 s s s。
转移的时候,我们需要分类讨论 i i i 的儿子数为 1 1 1 以及不小于 2 2 2 的情况。对于前者,转移形如 f i , s → f j , s f_{i,s} \to f_{j,s} fi,s→fj,s;对于后者,转移形如 f i , t + f i , s − t → f i , s ( t ∈ s ) f_{i,t}+f_{i,s-t} \to f_{i,s}(t \in s) fi,t+fi,s−t→fi,s(t∈s)。
于是,我们从小到大枚举 s s s,先通过枚举子集处理第二种情况,然后通过一遍 Dijkstra 对 f 1 , s , f 2 , s , ⋯ , f n , s f_{1,s},f_{2,s},\cdots,f_{n,s} f1,s,f2,s,⋯,fn,s 进行松弛。
时间复杂度 O ( 3 p n + m log m 2 p ) O(3^pn+m \log m 2^p) O(3pn+mlogm2p),其中 3 p 3^p 3p 为枚举子集的复杂度。
PS: 其实这就是最小斯坦纳树的板子。
算法二
对于多个频道的情况,我们该怎么办呢?
令 g S g_S gS 表示,所有频道属于集合 S S S 的情报站对应的最小斯坦纳树的大小。注意到,答案即为,挑选出若干个 S S S,在它们能够覆盖到所有的情报站的前提下,让它们的 g g g 之和最小。
预处理出每个 g S g_S gS,然后进行状压 dp \text{dp} dp 即可。
时间复杂度是什么呢?似乎是 O ( 2 p ( 3 p n + m log m 2 p ) ) O(2^p(3^pn+m \log m 2^p)) O(2p(3pn+mlogm2p)) 的,无法通过。但是,我们感觉严重跑不满,于是我们写了一个程序:
#include <bits/stdc++.h>
using namespace std;
const int maxl=15;
int n=1000,m=3000,ans;
int f[maxl],pow3[maxl],pow2[maxl];
signed main(){
pow3[0]=pow2[0]=1;
for (int i=1;i<=10;i++) pow3[i]=pow3[i-1]*3,pow2[i]=pow2[i-1]*2;
for (int i=1;i<=10;i++) f[i]=pow3[i]*n+pow2[i]*n*10;
for (int i=0;i<(1<<10);i++){
int sumv=0,cnt=0;
for (int j=1;j<=n;j++){
if (i&(1<<(j-1))) cnt+=j,sumv+=f[j];
}
ans=max(ans,sumv);
}
cout<<ans<<endl;
return 0;
}
发现这个值只有 1 0 8 10^8 108 左右,所以可以通过。
Code
#include <bits/stdc++.h>
#define int long long
#pragma GCC optimize("Ofast")
#define rg register
#define inf 1000000000000007
using namespace std;
const int maxn=1005,maxm=3005,maxk=15,maxs=1030;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,m,k,cnt,len;
int head[maxn],dis[maxn],vis[maxn],p[maxk],q[maxk],tmp[maxk];
int f[maxn][maxs],g[maxs];
struct edge{
int nxt,to,dis;}e[maxm<<1];
struct node{
int dis,pos;
bool operator < (const node &x) const{
return x.dis<dis;}
};
priority_queue<node> que;
vector<int> Bin;
void chkmin(int x,int &y){
y=min(x,y);}
void add_edge(int u,int v,int w){
e[++cnt].to=v,e[cnt].dis=w,e[cnt].nxt=head[u],head[u]=cnt;}
namespace Dream_Tree{
void dijkstra(){
while (!que.empty()){
int now=que.top().pos;que.pop();
if (vis[now]) continue;
vis[now]=1;
for (int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if (dis[y]>dis[now]+e[i].dis){
dis[y]=dis[now]+e[i].dis;
if (!vis[y]) que.push((node){
dis[y],y});
}
}
}
}
int solve(){
int k=Bin.size(),fi=0;
for (int i=1;i<=n;i++){
for (int j=0;j<(1<<k);j++) f[i][j]=inf;
}
for (int i=0;i<k;i++){
int now=Bin[i];
if (!fi) fi=Bin[i];
f[now][(1<<i)]=0;
}
for (int s=0;s<(1<<k);++s){
for (rg int i=1;i<=n;++i){
for (rg int t=(s&(s-1));t;t=(t-1)&s)
f[i][s]=min(f[i][s],f[i][t]+f[i][s^t]);
}
for (int i=1;i<=n;i++) vis[i]=0;
for (rg int i=1;i<=n;i++){
dis[i]=f[i][s];
que.push((node){
f[i][s],i});
}
dijkstra();
for (int i=1;i<=n;i++) f[i][s]=dis[i];
}
return f[fi][(1<<k)-1];
}
}
signed main(){
n=read(),m=read(),k=read();
for (int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
add_edge(u,v,w),add_edge(v,u,w);
}
for (int i=1;i<=k;i++){
q[i]=read(),p[i]=read();
tmp[++len]=q[i];
}
sort(tmp+1,tmp+len+1);
len=unique(tmp+1,tmp+len+1)-tmp-1;
for (int i=1;i<=k;i++) q[i]=lower_bound(tmp+1,tmp+len+1,q[i])-tmp;
for (int i=1;i<(1<<len);i++){
Bin.clear();
for (int j=1;j<=k;j++){
if (i&(1<<(q[j]-1))) Bin.push_back(p[j]);
}
g[i]=Dream_Tree::solve();
}
for (int i=0;i<(1<<len);i++){
for (int j=i;j;j=(j-1)&i) chkmin(g[j]+g[i^j],g[i]);
}
cout<<g[(1<<len)-1]<<endl;
return 0;
}