【原文链接】
(补发)题解|2023暑期杭电多校01
1002.City Upgrading
树形DP、支配集
题目大意
crazyzhk居住在一个树形城市。有一天,这个城市的网络需要升级。为了实现这一目标,需要部署路由器。
每个路由器可以覆盖其所在的节点及其相邻节点,在节点
i
i
i 上放置一个路由器的成本是
a
i
a_i
ai
确保每个节点都得到覆盖的情况下,成本最低是多少
前置知识点
- 树形DP
- 支配集:给定无向图 G = ( V , E ) G=(V,E) G=(V,E) ,称 V V V 的一个子集 S S S 为支配集,当且仅当对于 V − S V-S V−S 中任何一个点 v v v ,都有 S S S 中的某个点 u u u ,使得 ( u , v ) ∈ E (u,v) \in E (u,v)∈E
解题思路
要实现信号全覆盖,所选取的点集必须是一个支配集
题目求的是所有支配集中最小的cost,考虑树形DP
构造DP数组dp[N][3]
:
dp[i][0]
表示点 i i i 属于支配集合,并且以点 i i i 为根的子树都被覆盖了的情况下,支配集中所包含最少costdp[i][1]
表示点 i i i 不属于支配集合,且以 i i i 为根的子树都被覆盖,且 i i i 被其不少于一个子节点覆盖的情况下,支配集所包含最少costdp[i][2]
表示点 i i i 不属于支配集合,且以 i i i 为根的子树都被覆盖,且 i i i 没被子节点覆盖(将被父节点覆盖)的情况下,支配集中所包含最少cost
结合最小支配集的贪心思想,按DFS序的逆序进行DP,结点 i i i 的子结点记为 u u u
对于第一种状态,dp[i][0]
含义为点
i
i
i 属于支配集合,那么依次取每个儿子节点三种状态中的最小值,再把取得的最小值全部加起来再加1,就是dp[i][0]
的值了。即只要每个以
i
i
i 的儿子为根的子树都被覆盖,再加上当前点
i
i
i ,所需要的最少cost,DP转移方程如下:
d
p
[
i
]
[
0
]
=
1
+
∑
m
i
n
(
d
p
[
u
]
[
0
]
,
d
p
[
u
]
[
1
]
,
d
p
[
u
]
[
2
]
)
dp[i][0] = 1 + ∑min(dp[u][0], dp[u][1], dp[u][2])
dp[i][0]=1+∑min(dp[u][0],dp[u][1],dp[u][2])
对于第三种状态,dp[i][2]
含义为点
i
i
i 不属于支配集合,且
i
i
i 被其父节点覆盖。那么说明点
i
i
i 和点
i
i
i 的儿子节点都不属于支配集合,所以点
i
i
i 的第三种状态之和其儿子节点的第二种状态有关,方程为:
d
p
[
i
]
[
2
]
=
∑
d
p
[
u
]
[
1
]
dp[i][2] =∑dp[u][1]
dp[i][2]=∑dp[u][1]
对于第二种状态,略有些复杂。
首先如果点
i
i
i 没有子节点那么dp[i][1]
应该初始化为INF;否则为了保证它的每个以
i
i
i 的儿子为根的子树被覆盖,那么要取每个儿子节点的前两种状态的最小值之和,因为此时点
i
i
i 不属于支配集,不能支配其子节点,所以子节点必须已经被支配,与子节点的第三种状态无关。
如果当前所选状态中每个儿子都没被选择进入支配集,即在每个儿子的前两种状态中,第一种状态都不是所需点最小的,那么为了满足第二种状态的定义(因为点
i
i
i 的第二种状态必须被其子节点覆盖,即其子节点必须有一个属于支配集,如果此时没有,就必须强制使一个子节点的状态为状态一),需要重新选择点
i
i
i 的一个儿子节点为第一种状态,这时取花费最少的一个点,即取min(dp[u][0] - dp[u][1])
的儿子节点
u
u
u ,强制取其第一种状态,其他的儿子节点取第二种状态,DP转移方程为:
d
p
[
i
]
[
1
]
=
{
I
N
F
,
i
没有子节点
∑
m
i
n
(
d
p
[
u
]
[
0
]
,
d
p
[
u
]
[
1
]
)
+
i
n
c
,
O
t
h
e
r
w
i
s
e
dp[i][1] = \begin{cases} INF ,i没有子节点 \newline ∑min(dp[u][0], dp[u][1]) + inc ,Otherwise \newline \end{cases} \newline
dp[i][1]={INF,i没有子节点∑min(dp[u][0],dp[u][1])+inc,Otherwise
其中,
i
n
c
=
{
0
,上式的
O
t
h
e
r
w
i
s
e
中至少选择了一次状态一的子结点
m
i
n
(
d
p
[
u
]
[
0
]
−
d
p
[
u
]
[
1
]
)
,
O
t
h
e
r
w
i
s
e
其中,inc = \begin{cases} 0 ,上式的Otherwise中至少选择了一次状态一的子结点 \newline min(dp[u][0] - dp[u][1]) ,Otherwise \end{cases} \newline
其中,inc={0,上式的Otherwise中至少选择了一次状态一的子结点min(dp[u][0]−dp[u][1]),Otherwise
结果为根节点三个状态中的最小值
时间复杂度
O ( n l o g n ) O(nlogn) O(nlogn)
参考程序
#define N 2005
vector<vector<ll>> T;
vector<ll> cost;
vector<ll> DFS_vec;
void DFS(ll node){
DFS_vec.emplace_back(node);
for(auto i:T[node]) DFS(i);
}//获取DFS遍历序列
int solve()
{
ll n;
cin >> n;
T.clear();T.resize(n+1);
cost.clear();cost.resize(n+1);
FORLL(i,1,n) cin >> cost[i];
ll u,v;
FORLL(i,1,n-1){
cin >> u >> v;
T[u].emplace_back(v);
}
DFS_vec.clear();
DFS(1);
reverse(ALL(DFS_vec));//逆序
//print_vec(DFS_vec);
ll dp[N][3]={0};
for(auto i:DFS_vec)
{
dp[i][0]=cost[i];
dp[i][1]=0;
dp[i][2]=0;
for(auto j:T[i])
{
dp[i][0]+=min({dp[j][0],dp[j][1],dp[j][2]});
dp[i][2]+=dp[j][1];
}
if(T[i].empty()) dp[i][1]=INF;
else{
int flag=1;ll inc=INF;
for(auto j:T[i])
{
if(dp[j][0]<=dp[j][1]) {dp[i][1]+=dp[j][0];flag=0;}
else dp[i][1]+=dp[j][1];
}
if(flag){
for(auto j:T[i]) inc=min({inc,dp[u][0]-dp[u][1]});
dp[i][1]+=inc;
}
}
}
ll re=min({dp[1][0],dp[1][1]});
cout << re << endl;
return 0;
}
1005.Cyclically Isomorphic
字符串匹配
题目大意
对于两个字符串
s
1
s_1
s1 ,
s
2
s_2
s2 ,如果存在一个整数
k
k
k 使得
s
1
s_1
s1 循环右移k位与
s
2
s_2
s2 相同,则称他们是"cyclically right-shifted"。
每组数据给出
n
n
n 个长度为
m
m
m 的字符串,
Q
Q
Q 次询问两个字符串是否"cyclically right-shifted"。
解题思路
对于每一字符串,找到其字典序最小的状态储存,每次询问时直接比较即可。
主要是时间复杂度难以证明
时间复杂度
找最小态储存
n
×
m
2
n \times m^2
n×m2 ,比较
Q
×
m
Q \times m
Q×m
Q
≤
1
2
n
(
n
−
1
)
⇒
Q \le \dfrac{1}{2} n(n-1) \Rightarrow
Q≤21n(n−1)⇒ 比较
m
×
n
2
m \times n^2
m×n2
总复杂度:
O
(
m
n
×
(
m
+
n
)
)
O(mn \times (m+n))
O(mn×(m+n)) ,
m
n
≤
1
0
5
mn \le 10^5
mn≤105 下数据不变态的话就能卡过去
参考程序
int solve()
{
string s[N],s1,s2;
char t;
ll n,m;
cin >> n >> m;
FORLL(i,1,n)
{
cin >> s1;
s2=s1;
FORLL(j,1,m)
{
t=s2.front();
s2.erase(s2.begin());
s2.push_back(t);
if(s1.compare(s2)>0) s1=s2;
}
s[i]=s1;
}
ll Q;cin >> Q;
while(Q--)
{
cin >> n >> m;
if(s[n].compare(s[m])==0) cout << YES;
else cout << NO;
}
return 0;
}
1009.Assertion
签到题
题目大意
多组数据
给定m件物品,分成n组
问:是否无论怎么分都至少有一组个数超过d
解题思路
平均分组,找最大那个组的个数和d比较
参考程序
int solve()
{
ll m,n,d;
cin >> n >> m >> d;
if((m-1)/n+1>=d) cout << YES;
else cout << NO;
return 0;
}