HDOJ题目页面传送门
给定一个无向带权图(G=(V,E),|V|=n,|E|=m),求边权之和最大的有(s)个节点的链的边权之和,即求(maxlimits_{forall iin[1,s],forall jin(i,s],a_i e a_j,forall iin[1,s),(a_i,a_{i+1},l_i)in E}left{sumlimits_{i=1}^{s-1}l_i ight})。如果没有符合要求的链,输出( exttt{impossible})。
(ninleft[2,10^4 ight],minleft[1,10^4 ight],sin[2,6])。本题多测,最多有(35)组数据,(max(n,m)>100)的最多有(5)组。时限(15mathrm s)。
先说(1)种不是正解的玄学方法
暴搜。。。时间复杂度(mathrm O!left(dbinom ns ight)),但很显然非常跑不满,因为这是个稀疏图。而且还可以最优性剪枝优化,假设所有边中最大的边权为(longest),当前的最大答案为(ans),当前还剩(rst)个节点(条边)要搜,目前的边权之和为(now),那么如果(now+rst imes longestle ans),便可立即停止搜索。这样就可以水过去了。。。
这不是主要内容,时间复杂度到底是多少就不研究了,代码也不贴了。
正解
考虑对一个比原问题弱一点的问题求解:给每个节点(i)染一个([0,s))的颜色(col_i),求边权之和最大的有(s)个节点且这(s)个节点的颜色是(0sim s-1)的一个排列的链的边权之和。这个问题很好求,状压DP就可以了。设(dp_{i,j})表示边权之和最大的以(i)为(1)个端点,上面节点颜色互不相同且bitmask为(j)的链的边权之和,边界为(dp_{i,{j}}=egin{cases}0&i=j\-infty&i
e jend{cases}),状态转移方程为(dp_{i,j}=egin{cases}maxlimits_{(i,k,l)in E}left{dp_{k,j-{col_i}}+l
ight}&col_iin j\-infty&col_i
otin jend{cases}),最终答案为(maxlimits_{i=1}^{n}{dp_{i,[1,n]capmathbb Z}})。DP顺序要注意,要按照(j)的递增顺序DP,不能按照(i)的顺序(我就在上面WA过)。(简单DP
那么既然这个弱问题这么好求,那我们就强制给每个节点染色。假设我们给每个节点随机染色,那我们来分析一下弱问题的答案就是原问题的正确答案的概率:如果原问题所求得的最长链,在弱问题中,上面所有节点的颜色正好组成(0sim s-1)的一个排列,那么弱问题的答案就是原问题的答案,而在所有(s^s)种最长链的颜色序列中,有(s!)种是(0sim s-1)的排列,所以概率为(dfrac{s!}{s^s})。当(s=6)时,概率最小为(dfrac{6!}{6^6}approx1.5\%)。那么我们只需要在(35)组数据中每组随机染色、求解足够多次,就有足够大的的概率在每组中都至少有一次弱问题的答案是原问题的答案,这样把每次弱问题的答案(max)起来即可。我们希望这个概率至少为(99\%),设随机(x)次,那么可以列出不等式(left(1-left(1-dfrac{6!}{6^6} ight)^x ight)^{35}ge99\%)。不难解出(xge524.397cdots)。但由于HDOJ比较卡常,随机(525)次会TLE,于是我们只能用正确率换时间,只随机(300)次,这样才能勉强卡进(15mathrm s)的时限。此时正确率为(left(1-left(1-dfrac{6!}{6^6} ight)^{300} ight)^{35}approx71.8\%),侥幸过。
btw,随机的时候不能写rand()*rand()%s
。第一,在某些系统下两个rand()
乘起来会爆int;第二,把两个随机数乘起来,期望因数个数会增加很多,(mod s)的结果等于(0)的概率也就会增加很多,就不均匀了。可以这样写:abs(rand()*rand()*rand()+rand())%s
。
UPD 2020.4.12:你看看,ycx,你看看,上面那段删除线写的是smjbwy,狗屁不通。。看来我还是太年轻了。昨天经hsc神仙的提醒才知道,abs(rand()*rand()*rand()+rand())
这样的写法会生成出来分布极度不均匀的随机数,虽然在本题里没事,但显然不应该这么写。不如直接用mt19937
,先定义一个随机数生成器mt19937 rng;
,那么([l,r])之间的随机数就是uniform_int_distribution<>(l,r)(rng)
。
下面贴正解的代码:(UPDed 2020.4.12)
#include<bits/stdc++.h>
using namespace std;
#define int long long//爆int
#define pb push_back
#define mp make_pair
#define X first
#define Y second
#define uid uniform_int_distribution
const int inf=0x3f3f3f3f3f3f3f3f;
mt19937 rng(20060617/*信仰优化*/);
int ppc(int x){return __builtin_popcount(x);}
const int N=10000,S=6;
int n/*节点数*/,m/*边数*/,s/*要求的链的节点数*/;
vector<pair<int,int> > nei[N+1];//邻接表
int col[N+1];//颜色
int ans;//原问题答案
int dp[N+1][1<<S];
void random(){
for(int i=1;i<=n;i++)col[i]=uid<>(0,s-1)(rng);//随机染色
for(int i=1;i<=n;i++)for(int j=1;j<1<<s;j++)dp[i][j]=j==1<<col[i]?0:-inf;//处理边界,顺便初始化
for(int j=1;j<1<<s;j++)for(int i=1;i<=n;i++)if(j&1<<col[i]&&ppc(j)>1/*大小为1的bitmask是边界*/)//枚举状态
for(int k=0;k<nei[i].size();k++){//转移
int x=nei[i][k].X,len=nei[i][k].Y;
dp[i][j]=max(dp[i][j],dp[x][j^1<<col[i]]+len);
}
for(int i=1;i<=n;i++)ans=max(ans,dp[i][(1<<s)-1]);//把每次弱问题的答案max起来就有大概率是原问题的答案
}
void mian(){
cin>>n>>m>>s;
for(int i=1;i<=n;i++)nei[i].clear();//数据不清空,爆零两行泪
while(m--){
int x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
nei[x].pb(mp(y,z));nei[y].pb(mp(x,z));
}
ans=-inf;//初始化
int randomnum=300;//随机次数
while(randomnum--)random();//随机
if(ans>=0)cout<<ans<<"
";
else puts("impossible");
}
signed main(){
int testnum;//数据组数
cin>>testnum;
while(testnum--)mian();
return 0;
}