传送门
本题有多种方法。
方法一:采用状压dp,用 S S S记录点集,我们考虑设 d p [ S ] dp[S] dp[S]为一个合法的 D A G DAG DAG点集 S S S对应的最小答案。然后我们容易发现图中所有点的点权的范围一定可以位于 [ 0 , n − 1 ] [0,n-1] [0,n−1],再大一定不会更优。而且由于是一个 D A G DAG DAG,根据题意我们从 D A G DAG DAG的入度为零的点出发沿着边走到终点,这个过程中遇到的点的点权一定是递减的。
现在考虑 d p [ S ] dp[S] dp[S]已经求解出来,那么如何用 d p [ S ] dp[S] dp[S]去更新其它的状态,设 r e s res res为 S S S的补集,那么我们需要从 r e s res res中选择一个子集 r e s ′ res' res′,满足 r e s ′ res' res′中的所有点的入边对应的那个端点位于 S S S以内,那么 r e s ′ res' res′可以与 S S S合并为一个新集合 S ′ S' S′,也是一个dp的新状态,符合它是一个 D A G DAG DAG的条件。然后我们并不是一次性求出每条边的贡献,而是将贡献分配到每一步更新状态中,比如对于 S S S中的点而言,从 S S S到 S ′ S' S′我们会增加从 S S S中的点出发到达 r e s res res中所有点的边权之和(不太好描述,可以根据代码意会),这样做一定是正确的,我们相当于是在分层图之间 d p dp dp,对于层数靠后的点,每靠近它们一层我们加一次它们跟现在已有的点的边权贡献,相当于总贡献就是 层 数 之 差 ∗ 边 权 层数之差*边权 层数之差∗边权,也就是说只要还没 d p dp dp到它们就会一直被计算贡献,是符合题目给出的贡献定义的。
那么现在我们还需要预处理 v a l [ S ] val[S] val[S]数组,也就是每次 d p dp dp该加的贡献,考虑 v a l [ S ] val[S] val[S]也可以被递推求解,满足 v a l [ S ] = v a l [ S − l o w b i t ( S ) ] + ∑ 从 S 中 的 点 到 r e s 中 的 点 w − ∑ 从 S 中 的 点 到 l o w b i t ( S ) w val[S]=val[S-lowbit(S)]+\sum_{从S中的点到res中的点} w-\sum_{从S中的点到lowbit(S)}w val[S]=val[S−lowbit(S)]+∑从S中的点到res中的点w−∑从S中的点到lowbit(S)w。
具体实现代码的时候, i n e [ S ] ine[S] ine[S]代表状态 S S S对应的入边的端点集合。
int w[maxn][maxn],ine[1<<maxn],id[1<<maxn],path[1<<maxn],a[maxn];
ll val[1<<maxn],dp[1<<maxn];
int main(){
int n=rd(),m=rd();
FOR(i,1,m+1){
int u=rd()-1,v=rd()-1;
w[u][v]=rd();
ine[1<<v]|=1<<u;
}
FOR(i,0,n)id[1<<i]=i;
FOR(i,1,1<<n){
int u=id[i&-i];
ll wvu=0,wuv=0;
FOR(j,0,n){
if((1<<j)&i){
wvu+=w[j][u];
}else wuv+=w[u][j];
}
val[i]=val[i-(i&-i)]+wuv-wvu;
ine[i]=(ine[i-(i&-i)]|ine[i&-i]);
}
FOR(i,1,1<<n)dp[i]=INF;
FOR(i,0,(1<<n)-1){
if(dp[i]==INF)continue;
int res=((1<<n)-1)^i;
for(register int j=res;j;j=(j-1)&res){
if((ine[j]&i)==ine[j] && dp[j|i]>dp[i]+val[i]){
dp[j|i]=dp[i]+val[i];
path[j|i]=i;
}
}
}
int cur=(1<<n)-1,now=0;
while(cur){
int last=path[cur];
int d=cur^last;
FOR(j,0,n)if((1<<j)&d)a[j]=now;
now++;
cur=last;
}
FOR(i,0,n)printf("%d ",a[i]);
puts("");
}
方法二: