头像

nooprush




离线:27分钟前


最近来访(82)
用户头像
潘瑞杰
用户头像
今天一定早点睡
用户头像
企鹅聊天图书馆摇头晃
用户头像
菜狗逆袭记
用户头像
原语
用户头像
mofa世界大magua
用户头像
acwing_1183
用户头像
猪蹄
用户头像
吃不月半的fcano
用户头像
嘎嘎嘎嘎嘎嘎嘎
用户头像
量子态发圈
用户头像
略略略.
用户头像
竪先生
用户头像
Yuze_Neko
用户头像
王一然
用户头像
pront
用户头像
再写一道题就睡
用户头像
空_65
用户头像
Twinkle_wuwu
用户头像
krio


差分约束

1.该题求的是最大值,也就是说求整条路径所有点的表达式上界的最小值,也就是说求每个点的最短路径,对于xi点的表达式来说,xi的表达式应该诸如xi<=xj+c类型,那么我们就可以考虑将题面信息的边权关系转变为表达式的类型。

2.因为奶牛按照编号顺序排列,并且可能在同一点上,表达式也就是i<=i+1,也即i<=i+1 +0,转换为加边函数即add(i+1,i,0)

3.对于两头奶牛,如果二者好感距离10,说明二者距离相减的绝对值小于等于10,为减少麻烦,我们可以默认b比a大,也就是当a>b时,我们选择调换二者位置 swap(a,b).

那么二者好感距离为10,变成xi的表达形式,也就是b<=a+10,对应的加边函数即add(a,b,10)

4.对于两头奶牛,如果二者厌恶距离10,也就是说二者距离相减的绝对值大于等于10,为减少麻烦,我们可以默认b比a大,也就是当a>b时,我们选择调换二者位置 swap(a,b).

那么二者厌恶距离为10,变成xi的表达形式,也就是a<=b-10,转换为加边函数即add(b,a,-10)

5.将所有边作为起点(相当于虚拟源点的思路),执行一遍SPFA,如果存在负环,说明存在矛盾,无法进行布局,输出-1

6.将1号点作为起点,走一遍SPFA,如果最后dist[n]==inf,说明N号点1号点之间不存在任何大小限制关系,那么自然N号点到1号点的最大距离可以为无穷大,输出-2

7.如果最后dist[n]不等于inf,说明1号点与N号点之间存在距离大小限制关系,并且根据上述几点的分析,我们可以得到1号点到N号点的最大距离

# 1.只要存在正环/负环, 说明不存在方案
# 2.该题求的是最大值,即求最短路径,即所有上界的最小值,得到的xi关系都是诸如 xi<=xj+c 

from collections import deque

N = 1010
M = 10000+10000+1000+10
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
inf = float('inf')
idx = 0

def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx] = c
    h[a] = idx
    idx+=1

def spfa(size):
    global dist
    dist = [inf]*N
    st = [False]*N
    cnt = [0]*N
    q = deque()
    for i in range(1,size+1):
        q.append(i)
        st[i] = True
        dist[i] = 0

    while(q):
        t = q.popleft()
        st[t] = False
        i = h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]>dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
                cnt[j] = cnt[t]+1
                if cnt[j]>=n:
                    return True

                if not st[j]:
                    st[j] = True
                    q.append(j)

            i = ne[i]

    return False

n,l,d = map(int,input().split())
for i in range(l):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(a,b,c)
for i in range(d):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(b,a,-c)

for i in range(1,n):
    add(i+1,i,0)

aa = spfa(n)

if aa:
    print(-1)
else:
    spfa(1)
    if dist[n]==inf:
        print(-2)
    else:
        print(dist[n])

虚拟源点做法

# 1.只要存在正环/负环, 说明不存在方案
# 2.该题求的是最大值,即求最短路径,即所有上界的最小值,得到的xi关系都是诸如 xi<=xj+c 

from collections import deque

N = 1010
M = 10000+10000+1000+10
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
inf = float('inf')
idx = 0

def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx] = c
    h[a] = idx
    idx+=1

def spfa(x):
    global dist
    dist = [inf]*N
    st = [False]*N
    cnt = [0]*N
    q = deque()
    q.append(x)
    st[x] = True
    dist[x] = 0


    while(q):
        t = q.popleft()
        st[t] = False
        i = h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]>dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
                cnt[j] = cnt[t]+1
                if cnt[j]>=n+1:
                    return True

                if not st[j]:
                    st[j] = True
                    q.append(j)

            i = ne[i]

    return False

n,l,d = map(int,input().split())
for i in range(l):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(a,b,c)
for i in range(d):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(b,a,-c)

for i in range(1,n):
    add(i+1,i,0)

for i in range(1,n+1):
    add(0,i,0) #虚拟源点

aa = spfa(0)

if aa:
    print(-1)
else:
    spfa(1)
    if dist[n]==inf:
        print(-2)
    else:
        print(dist[n])





活动打卡代码 AcWing 1170. 排队布局

差分约束

1.该题求的是最大值,也就是说求整条路径所有点的表达式上界的最小值,也就是说求每个点的最短路径,对于xi点的表达式来说,xi的表达式应该诸如xi<=xj+c类型,那么我们就可以考虑将题面信息的边权关系转变为表达式的类型。

2.因为奶牛按照编号顺序排列,并且可能在同一点上,表达式也就是i<=i+1,也即i<=i+1 +0,转换为加边函数即add(i+1,i,0)

3.对于两头奶牛,如果二者好感距离10,说明二者距离相减的绝对值小于等于10,为减少麻烦,我们可以默认b比a大,也就是当a>b时,我们选择调换二者位置 swap(a,b).

那么二者好感距离为10,变成xi的表达形式,也就是b<=a+10,对应的加边函数即add(a,b,10)

4.对于两头奶牛,如果二者厌恶距离10,也就是说二者距离相减的绝对值大于等于10,为减少麻烦,我们可以默认b比a大,也就是当a>b时,我们选择调换二者位置 swap(a,b).

那么二者厌恶距离为10,变成xi的表达形式,也就是a<=b-10,转换为加边函数即add(b,a,-10)

5.将所有边作为起点(相当于虚拟源点的思路),执行一遍SPFA,如果存在负环,说明存在矛盾,无法进行布局,输出-1

6.将1号点作为起点,走一遍SPFA,如果最后dist[n]==inf,说明N号点1号点之间不存在任何大小限制关系,那么自然N号点到1号点的最大距离可以为无穷大,输出-2

7.如果最后dist[n]不等于inf,说明1号点与N号点之间存在距离大小限制关系,并且根据上述几点的分析,我们可以得到1号点到N号点的最大距离

# 1.只要存在正环/负环, 说明不存在方案
# 2.该题求的是最大值,即求最短路径,即所有上界的最小值,得到的xi关系都是诸如 xi<=xj+c 

from collections import deque

N = 1010
M = 10000+10000+1000+10
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
inf = float('inf')
idx = 0

def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx] = c
    h[a] = idx
    idx+=1

def spfa(size):
    global dist
    dist = [inf]*N
    st = [False]*N
    cnt = [0]*N
    q = deque()
    for i in range(1,size+1):
        q.append(i)
        st[i] = True
        dist[i] = 0

    while(q):
        t = q.popleft()
        st[t] = False
        i = h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]>dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
                cnt[j] = cnt[t]+1
                if cnt[j]>=n:
                    return True

                if not st[j]:
                    st[j] = True
                    q.append(j)

            i = ne[i]

    return False

n,l,d = map(int,input().split())
for i in range(l):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(a,b,c)
for i in range(d):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(b,a,-c)

for i in range(1,n):
    add(i+1,i,0)

aa = spfa(n)

if aa:
    print(-1)
else:
    spfa(1)
    if dist[n]==inf:
        print(-2)
    else:
        print(dist[n])

虚拟源点做法

# 1.只要存在正环/负环, 说明不存在方案
# 2.该题求的是最大值,即求最短路径,即所有上界的最小值,得到的xi关系都是诸如 xi<=xj+c 

from collections import deque

N = 1010
M = 10000+10000+1000+10
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
inf = float('inf')
idx = 0

def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx] = c
    h[a] = idx
    idx+=1

def spfa(x):
    global dist
    dist = [inf]*N
    st = [False]*N
    cnt = [0]*N
    q = deque()
    q.append(x)
    st[x] = True
    dist[x] = 0


    while(q):
        t = q.popleft()
        st[t] = False
        i = h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]>dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
                cnt[j] = cnt[t]+1
                if cnt[j]>=n+1:
                    return True

                if not st[j]:
                    st[j] = True
                    q.append(j)

            i = ne[i]

    return False

n,l,d = map(int,input().split())
for i in range(l):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(a,b,c)
for i in range(d):
    a,b,c = map(int,input().split())
    if a>b:a,b = b,a
    add(b,a,-c)

for i in range(1,n):
    add(i+1,i,0)

for i in range(1,n+1):
    add(0,i,0) #虚拟源点

aa = spfa(0)

if aa:
    print(-1)
else:
    spfa(1)
    if dist[n]==inf:
        print(-2)
    else:
        print(dist[n])





活动打卡代码 AcWing 1169. 糖果

差分约束,关键在于将整个题面的信息转换为点权建边的信息。

1.若X=1,则建立边(u,v,0),(v,u,0) 表示两者相等.
2.若X=2,则建立边(u,v,1),表示v比u大一.
3.若X=3,则建立边(v,u,0),表示u大于等于v.
4.若X=4,则建立边(v,u,1),表示u比v大一.
5.若X=5,则建立边(u,v,0),表示u小于等于v.

因为求的是最小值,那么我们求的就是最长路,即所有下界的最大值

每个x的关系可以表示为xi>=xj+c,对于建边来说就是add(xj,xi,c),所以对于xi>=xj的情况,我们可以认为是xi = xj + 0,建边的方法就是 add(xj,xi,0) ,而不能是add(xi,xj,0),这样表示的就是xi<=xj的情况。

注意建边方向,因为要保证最优性,所以从小的往最大的转移,再跑裸的spfa.

from collections import deque
N = 100010
M = 300010
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
idx =  0
inf = float('-inf')
dist = [inf]*N
cnt = [0]*N
st = [False]*N


def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx] = c
    h[a] = idx
    idx+=1

def spfa():
    q = deque()
    q.append(0)
    dist[0] = 0
    cnt[0] = 0
    st[0] = True
    while q:
        t = q.popleft()
        st[t] = False
        i = h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]<dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
                cnt[j] = cnt[t]+1
                if cnt[j]>=n+1: #多加了一个虚拟源点,所以到达某点的路程为n是可能的,所以判断条件要变为>=n+1
                    return False
                if not st[j]:
                    st[j] = True
                    q.append(j)

            i = ne[i]

    return True



n,k = map(int,input().split())
for i in range(k):
    x,a,b = map(int,input().split())
    if x==1:
        add(a,b,0)
        add(b,a,0)
    elif x==2:
        add(a,b,1)
    elif x==3:
        add(b,a,0)
    elif x==4:
        add(b,a,1)
    elif x==5:
        add(a,b,0)

for i in range(1,n+1):
    add(0,i,1)

ss = spfa()
if not ss:
    print(-1)
else:
    res = 0
    for i in range(1,n+1):
        res+=dist[i]

    print(res)


活动打卡代码 AcWing 1192. 奖金

拓扑排序

from collections import deque

N = 10010
M = 30010
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
dist = [0]*N
idx = 0
d = [0]*N
ans = []

def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx]  =c
    h[a] =idx
    idx+=1

def topsort():
    global ans
    q = deque()
    for i in range(n+1):
        if d[i]==0:
            q.append(i)

    while q:
        t = q.popleft()
        ans.append(t)
        i = h[t]
        while(i!=-1):
            j = e[i]
            d[j]-=1
            if d[j]==0:
                q.append(j)
            i = ne[i]

    return len(ans)==n+1



n,m = map(int,input().split())
for i in range(m):
    a,b = map(int,input().split())
    add(b,a,1)
    d[a]+=1

for i in range(1,n+1):
    add(0,i,100)
    d[i]+=1

s = topsort()
if s:

    for t in ans:
        i =h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]<dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
            i =ne[i]

    res =0
    for i in range(1,n+1):
        res+=dist[i]

    print(res)

else:
    print('Poor Xed')

spfa判断正环

from collections import deque

N = 10010
M = 30010
e = [0]*M
ne = [0]*M
h = [-1]*N
w = [0]*M
dist = [0]*N
cnt = [0]*N
st = [False]*N
idx = 0

def add(a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx] = c
    h[a] = idx
    idx+=1

def spfa():
    q = deque()
    q.append(0)
    st[0] = True
    while(q):
        t = q.popleft()
        st[t] = False
        i = h[t]
        while(i!=-1):
            j = e[i]
            if dist[j]<dist[t]+w[i]:
                dist[j] = dist[t]+w[i]
                cnt[j]  = cnt[t]+1
                if cnt[j]>=n+1:
                    return False
                if not st[j]:
                    st[j] = True
                    q.append(j)

            i = ne[i]

    return True


n,m = map(int,input().split())
for i in range(m):
    a,b = map(int,input().split())
    add(b,a,1)

for i in range(1,n+1):
    add(0,i,100)

t = spfa()
if (t):
    res = 0
    for i in range(1,n+1):
        res+=dist[i]

    print(res)
else:
    print('Poor Xed')

tarjan缩点

N = 10010
M = 60010
e = [0]*M
ne = [0]*M
h1 = [-1]*N
h2 = [-1]*N
w = [0]*M
idx = 0

stk = [0]*N
idx_stk = 0
in_stk = [False]*N
timestamp = 0
scc_cnt = 0
id = [0]*N
scc_size = [0]*N
dist = [0]*N
dfn = [0]*N
low = [0]*N


def add(h,a,b,c):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    w[idx]  = c
    h[a]= idx
    idx+=1

def tarjan(u):
    global timestamp,scc_cnt,idx_stk
    timestamp+=1
    dfn[u] = low[u] = timestamp
    i = h1[u]
    idx_stk+=1
    stk[idx_stk] = u
    in_stk[u] = True

    while(i!=-1):
        j = e[i]
        if not dfn[j]:
            tarjan(j)
            low[u] = min(low[u],low[j])
        elif in_stk[j]:
            low[u] = min(low[u],dfn[j])

        i = ne[i]


    if dfn[u]==low[u]:
        scc_cnt+=1
        while True:
            y = stk[idx_stk]
            idx_stk-=1
            id[y] = scc_cnt
            in_stk[y] = False
            scc_size[scc_cnt]+=1
            if y==u:
                break

n,m = map(int,input().split())
for i in range(m):
    a,b = map(int,input().split())
    add(h1,b,a,1)

for i in range(1,n+1):
    add(h1,0,1,100)

for i in range(n+1):
    if not dfn[i]:
        tarjan(i)

flag = True
for t in range(n+1):
    i = h1[t]
    while(i!=-1):
        j = e[i]
        id1 = id[t]
        id2 = id[j]
        if id1==id2:
            if w[i]:
                flag = False
                break
        else:
            add(h2,id1,id2,w[i])

        i = ne[i]

    if not flag:
        break

if not flag:
    print('Poor Xed')

else:
    dist[0] = 0
    res = 0
    for i in range(scc_cnt,0,-1):
        j = h2[i]
        while(j!=-1):
            k = e[j]
            if dist[k]<dist[i]+w[j]:
                dist[k] = dist[i]+w[j]

            j = ne[j]

    #print(dist)
    #print(scc_cnt)
    for i in range(1,scc_cnt+1):
        res+=dist[i]*scc_size[i]

    print(res)




活动打卡代码 AcWing 426. 开心的金明

N = 30010
f = [0]*N
M = 30
v = [0]*M
w = [0]*M

n,m = map(int,input().split())
for i in range(1,m+1):
    a,b = map(int,input().split())
    v[i] = a
    w[i] = b

for i in range(1,m+1):
    for j in range(n,v[i]-1,-1):
        f[j] = max(f[j],f[j-v[i]]+v[i]*w[i])

print(f[n])



活动打卡代码 AcWing 7. 混合背包问题

思路 缝合背包

对于每一个物品,我们只需要考虑其属于哪一种背包类型,然后执行该背包类型的求法即可,最后得出的f[m]即为混合背包的最优解

N = 1010
f = [0]*N
v = [0]*N
w = [0]*N
s = [0]*N

n,m = map(int,input().split())
for i in range(1,n+1):
    a,b,c = map(int,input().split())
    v[i] = a
    w[i] = b
    s[i] = c

for i in range(1,n+1):# 枚举物品
    if s[i]==0: #判断是哪种类型,进而按照类型枚举体积
        for j in range(v[i],m+1):
            f[j] = max(f[j],f[j-v[i]]+w[i])

    else:
        if s[i]==-1:
            ss = 1
        else:
            ss = s[i]

        k = 1
        while(k<=ss):
            for j in range(m,k*v[i]-1,-1): #将多重背包按照二倍打包,然后执行01背包选或不选
                f[j] = max(f[j],f[j-k*v[i]]+k*w[i])

            ss-=k
            k*=2

        if ss:
            for j in range(m,ss*v[i]-1,-1):
                f[j] = max(f[j],f[j-ss*v[i]]+ss*w[i])

print(f[m])





活动打卡代码 AcWing 532. 货币系统

极大无关组

基本思路:先把a数组按照从小到大的顺序排序,之所以要排序做,是因为这样保证了后面的数字如果能够凑出,必然被前面比它小的数字已经凑出,所以如果能够凑出我们就无需对res+1。

T = int(input())
N = 25010
for i in range(T):
    f = [False]*N
    n  =int(input())
    a = list(map(int,input().split()))
    a.sort()
    m = a[-1]
    res = 0

    f[0] = 1
    for i in range(n):
        if (not f[a[i]]):
            res+=1

        for j in range(a[i],m+1):
            f[j] |= f[j-a[i]]

    print(res)





dp求方案 无需按照字典序排序做法

从最后一个结果的状态,反推现阶段的结果由哪个前面的结果得出,并且更新现阶段的状态(也就是体积大小),以此从后往前依次推出每次选择的方案。

N = 20
w = [[0]*N for i in range(N)]
f = [[0]*N for i in range(N)] #f[i,j]
path = []

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

for i in range(1,n+1): #正向执行DP
    for j in range(m+1):
        f[i][j] = f[i-1][j]
        for k in range(j+1):
            f[i][j] = max(f[i][j],f[i-1][j-k]+w[i][k])

print(f[n][m])

j = m
for i in range(n,0,-1): #倒推
    for k in range(j+1):
        if f[i][j] == f[i-1][j-k]+w[i][k]:
            print(i,k)
            j-=k
            break



通过拓扑图求最短路思路得出方案做法

我们知道,动态规划实际上是对拓扑图求最短路径,这里的最短路径也即该题中的的最大价值,那么我们就可以参考最短路求方案的思想根据上一个阶段以及该阶段的关系来得出选择的方案。

N = 20
w = [[0]*N for i in range(N)]
f = [[0]*N for i in range(N)] #f[i,j]
path = []

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

def dfs(i,j):
    if (not i):return

    for k in range(j+1):
        if f[i][j] == f[i-1][j-k]+w[i][k]:
            print(i,k)
            dfs(i-1,j-k)
            return


for i in range(1,n+1):
    for j in range(m+1):
        f[i][j] = f[i-1][j]
        for k in range(j+1):
            f[i][j] = max(f[i][j],f[i-1][j-k]+w[i][k])

print(f[n][m])

dfs(n,m)

字典序做法

该题并没有要求我们通过字典序排序来做,但是如果要求我们按照字典序排序来输出结果,我们可以参考背包问题求具体方案的写法,反向执行dp,值得注意的是无论是正向dp还是反向dp,得到的最终的最大价值都是一样的,但是反向执行dp,让我们可以通过从较高的位置来转移得到较低位置的答案,那么我们就可以通过对比低端高端之间的转移关系来思考是否选择该条路径,由于要满足字典序,所以当我们发现可以选择的时候就必须选择该条路径

N = 20
w = [[0]*N for i in range(N)]
f = [[0]*N for i in range(N)]

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

for i in range(n,0,-1): # 反向执行DP
    for j in range(m+1):
        for k in range(j+1):
            f[i][j] = max(f[i][j],f[i+1][j-k]+w[i][k])

print(f[1][m])

j = m
res = [0]*N
for i in range(1,n+1): #倒推,由于是反向执行的,从1开始
    for k in range(j+1):
        if f[i][j]==f[i+1][j-k]+w[i][k]:
            res[i] = k
            j-=k
            break
for i in range(1,n+1):
    print(i,res[i])

DFS暴力解法

对DFS暴力的详细解释:小猫爬山

N = 20
w = [[0]*N for i in range(N)]

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

maxv = 0
res = []
def dfs(u,sums,cnt,v):
    global maxv,res

    if (u>n):
        if sums>maxv:
            maxv = sums
            res = v.copy()
        return

    for i in range(cnt+1):
        v.append(i)
        dfs(u+1,sums+w[u][i],cnt-i,v)
        v.pop()

dfs(1,0,m,[0])

print(maxv)
for i in range(1,n+1):
    print(i,res[i])




活动打卡代码 AcWing 1013. 机器分配

dp求方案 无需按照字典序排序做法

从最后一个结果的状态,反推现阶段的结果由哪个前面的结果得出,并且更新现阶段的状态(也就是体积大小),以此从后往前依次推出每次选择的方案。

N = 20
w = [[0]*N for i in range(N)]
f = [[0]*N for i in range(N)] #f[i,j]
path = []

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

for i in range(1,n+1): #正向执行DP
    for j in range(m+1):
        f[i][j] = f[i-1][j]
        for k in range(j+1):
            f[i][j] = max(f[i][j],f[i-1][j-k]+w[i][k])

print(f[n][m])

j = m
for i in range(n,0,-1): #倒推
    for k in range(j+1):
        if f[i][j] == f[i-1][j-k]+w[i][k]:
            print(i,k)
            j-=k
            break



通过拓扑图求最短路思路得出方案做法

我们知道,动态规划实际上是对拓扑图求最短路径,这里的最短路径也即该题中的的最大价值,那么我们就可以参考最短路求方案的思想根据上一个阶段以及该阶段的关系来得出选择的方案。

N = 20
w = [[0]*N for i in range(N)]
f = [[0]*N for i in range(N)] #f[i,j]
path = []

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

def dfs(i,j):
    if (not i):return

    for k in range(j+1):
        if f[i][j] == f[i-1][j-k]+w[i][k]:
            print(i,k)
            dfs(i-1,j-k)
            return


for i in range(1,n+1):
    for j in range(m+1):
        f[i][j] = f[i-1][j]
        for k in range(j+1):
            f[i][j] = max(f[i][j],f[i-1][j-k]+w[i][k])

print(f[n][m])

dfs(n,m)

字典序做法

该题并没有要求我们通过字典序排序来做,但是如果要求我们按照字典序排序来输出结果,我们可以参考背包问题求具体方案的写法,反向执行dp,值得注意的是无论是正向dp还是反向dp,得到的最终的最大价值都是一样的,但是反向执行dp,让我们可以通过从较高的位置来转移得到较低位置的答案,那么我们就可以通过对比低端高端之间的转移关系来思考是否选择该条路径,由于要满足字典序,所以当我们发现可以选择的时候就必须选择该条路径

N = 20
w = [[0]*N for i in range(N)]
f = [[0]*N for i in range(N)]

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

for i in range(n,0,-1): # 反向执行DP
    for j in range(m+1):
        for k in range(j+1):
            f[i][j] = max(f[i][j],f[i+1][j-k]+w[i][k])

print(f[1][m])

j = m
res = [0]*N
for i in range(1,n+1): #倒推,由于是反向执行的,从1开始
    for k in range(j+1):
        if f[i][j]==f[i+1][j-k]+w[i][k]:
            res[i] = k
            j-=k
            break
for i in range(1,n+1):
    print(i,res[i])

DFS暴力解法

N = 20
w = [[0]*N for i in range(N)]

n,m = map(int,input().split())
for i in range(1,n+1):
    w[i][1:] = list(map(int,input().split()))

maxv = 0
res = []
def dfs(u,sums,cnt,v):
    global maxv,res

    if (u>n):
        if sums>maxv:
            maxv = sums
            res = v.copy()
        return

    for i in range(cnt+1):
        v.append(i)
        dfs(u+1,sums+w[u][i],cnt-i,v)
        v.pop()

dfs(1,0,m,[0])

print(maxv)
for i in range(1,n+1):
    print(i,res[i])





二进制优化版本

例子

将十进制数7前面的正整数包括7凑出来,最少可以通过log(7)上取整个数字来凑出。

例如,我们知道7以前的数包括自己当然可以从7个1中凑出来,数字多少我们就用多少个1凑出即可,但是这样是通过最笨的一种凑法,如果我们了解了二进制的内部含义,我们就可以发现 可以通过二进制的优化方式通过logn上取整的数字个数凑出任何一个数字n

7 就可以通过1,2,4这三个数字来凑出
1 = 1
2 = 2
3 = 1+2
4 = 4
5 = 1+4
6 = 2+4
7 = 1+2+4

同时,我们发现7的二进制表达为111,也正是说明,7的结果由2^0+2^1+2^2=1+2+4得出。6的二进制表达为110也就是说,6由2^1+2^2 = 2+4凑出,那么我们就可以通过二进制的优化方式,把一个很大数的前面所有数,通过一部分特定的二次幂数表达出来。

那么,对于一个特殊的情况,如果n=10,那么我们当然不能取到15(1111),这样的话,我们就多凑出了11,12,13,14,15这几个数,那么我们就可以在7(111)的时候,直接加上剩下的3即可,因为1~7已经被我们凑出,那么从中任意的数字+3都可以让我们完整的凑出1~10

那么对于如果组数取到最大,也就是2000,我们也只需要log(2000)上取整 这么多个数字,就可以表达出所有的大小。这样就不需要通过多重背包问题的原始的O(n^3)的时间复杂度做法,而是可以将所有凑的可行可能打包存入数组中,然后执行01背包问题就行。

python代码

N = 12000 # 开辟log2(2000)*1000
M = 2100
f = [0]*M
v = [0]*N
w = [0]*N

n,m = map(int,input().split())
cnt = 0 
for i in range(1,n+1):
    a,b,s = map(int,input().split())
    k = 1
    while (k<=s):
        cnt+=1
        v[cnt] = a*k
        w[cnt] = b*k
        s-=k
        k = k*2
    if s:
        cnt+=1
        v[cnt] = a*s
        w[cnt] = b*s

n = cnt
for i in range(1,cnt+1):
    for j in range(m,v[i]-1,-1):
        f[j] = max(f[j],f[j-v[i]]+w[i])

print(f[m])


对于二进制的常用操作扩展

二进制常用操作扩展