Day4 还是比较简单的
C.Journey
有一个DAG,边上有权值,共有n个点和m条边你一共有T的体力,现在从1出发到点n,经过一条边,花费体力值为这条边的边权,现在你要在体力允许的情况下到达n点并尽可能经过最多的点,求出经过点的数量和路线
DAG上dp,开始读错题了。。。以为终点随意。后来以为dfs遍历一下就ok,后来发现并不是,会爆掉(这是真的蠢)
d
p
/
i
,
j
dp/_{i,j}
dp/i,j表示在点
i
i
i处距离
n
n
n的最大深度为
j
j
j时需要的最小花费,则有:
d
p
n
o
w
,
i
=
m
i
n
(
d
p
n
o
w
,
i
,
d
p
t
o
,
i
−
1
+
d
i
s
(
n
o
w
,
t
o
)
)
dp_{now,i}=min(dp_{now,i},dp_{to,i-1}+dis(now,to))
dpnow,i=min(dpnow,i,dpto,i−1+dis(now,to))
当
d
p
n
o
w
,
i
dp_{now,i}
dpnow,i发生更新的时候,更新一下路径
p
a
t
h
n
o
w
,
i
=
t
o
path_{now,i}=to
pathnow,i=to
#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
using namespace std;
const int maxn=5e3+5;
const int inf=1e9+9;
vector<pii>G[maxn];
int dp[maxn][maxn];
int path[maxn][maxn];
int vis[maxn];
int n,m,T;
void dfs(int now){
for(pii t:G[now]){
int to=t.first,v=t.second;
if(!vis[to]){
vis[to]=1;
dfs(to);
}
for(int i=1;i<=n;++i){
if(dp[now][i]>dp[to][i-1]+v){
dp[now][i]=dp[to][i-1]+v;
path[now][i]=to;
}
}
}
}
int main()
{
ios::sync_with_stdio(0);
for(int i=0;i<maxn;++i) for(int j=0;j<maxn;++j) dp[i][j]=inf;
cin>>n>>m>>T;
for(int i=1;i<=m;++i){
int a,b,c;cin>>a>>b>>c;
G[a].push_back({b,c});
}
dp[n][1]=0;
dfs(1);
int dep;
for(int i=n;i>=1;--i){
if(dp[1][i]<=T){
dep=i;
cout<<i<<endl;
break;
}
}
int now=1;
for(int i=0;i<dep;++i){
cout<<now<<" ";
now=path[now][dep-i];
}
}
D.Maxim and Array
对于一个有n个元素的序列 A A A,你可以进行k次操作,每次可以把任意一个元素加上x。如何修改可以使 ∏ 1 i ≤ n \prod\limits_{1}^{i\leq n} 1∏i≤n最小
贪心,我们可以知道,修改最小的值,可以使得这次修改对当前答案的贡献最大。然后讨论一下正负,如果 ∏ 1 i ≤ n 0 \prod\limits_{1}^{i\leq n}0 1∏i≤n0,先看能不能把最小的变成负数,(其实只要一直绝对值处理最小的就ok,有一个优先队列维护)
#include <bits/stdc++.h>
#define ll long long
#define pli pair<ll ,int>
using namespace std;
const int maxn=2e5+5;
ll num[maxn];
ll abss(ll x)
{
return x < 0 ? -x : x;
}
struct cmp
{
bool operator()(pli a, pli b)
{
return abss(a.first) > abss(b.first);
}
};
priority_queue<pli,vector<pli>,cmp>pq;
int flag=0;
int main()
{
ios::sync_with_stdio(0);
int n, m, x;
cin >> n >> m >> x;
for(int i=1;i<=n;++i)
{
int t;cin>>t;
if(t<0) flag^=1;
pq.push({t,i});
}
if(flag)
{
for(int i=1;i<=m;++i)
{
ll t = pq.top().first;
int ti=pq.top().second;
pq.pop();
if (t < 0) t-=x;
else t+=x;
pq.push({t,ti});
}
}
else{
pli t=pq.top();
pq.pop();
ll tv=t.first;
int ti=t.second;
if(abss(tv)-1ll*m*x<0)
{
int need=abss(tv)/x+1;
m-=need;
if(tv<0) tv+=1ll*need*x;
else tv-=1ll*need*x;
pq.push({tv,ti});
for(int i=1;i<=m;++i)
{
pli v=pq.top();
ll vv=v.first;
int vi=v.second;
pq.pop();
if(vv<0) vv-=x;
else vv+=x;
pq.push({vv,vi});
}
}
else if(abss(tv)==1ll*m*x)
{
pq.push({0,ti});
} else
{
pq.push({tv-1ll*m*x,ti});
}
}
while(!pq.empty())
{
pli t=pq.top();
num[t.second]=t.first;
pq.pop();
}
for(int i=1;i<=n;++i) cout<<num[i]<<" ";
}
H.树上染色
有一个n个点的树,树边有正权值,你要把其中的k个点染色,求染色的点两两之间距离之和+未染色点两两之间距离之和
开始以为是二分图,后来发现,k是给定的,不知道怎么做了。。。然后网友解释是树形dp。参考题解
d
p
i
,
j
dp_{i,j}
dpi,j表示以i为顶点的子树染了j个点后,这棵子树对于答案的贡献是多少。在更新的时候,
t
m
p
i
tmp_i
tmpi表示:在当前这棵子树下,染色i个点对(当前子树给)答案的贡献,即
t
m
p
i
=
d
p
x
,
i
(
x
为
顶
点
)
tmp_i=dp_{x,i}(x为顶点)
tmpi=dpx,i(x为顶点)
对于每条边
(
u
,
v
)
(u,v)
(u,v)权值为c,则当前边对答案的贡献:
c
∗
(
(
u
那
一
端
连
接
的
染
色
点
∗
v
那
一
段
连
接
的
染
色
点
)
+
(
u
那
一
段
的
未
染
色
点
∗
那
一
端
的
未
染
色
点
)
)
c*((u那一端连接的染色点*v那一段连接的染色点)+(u那一段的未染色点*那一端的未染色点))
c∗((u那一端连接的染色点∗v那一段连接的染色点)+(u那一段的未染色点∗那一端的未染色点))
进行过更新即可
#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
const int maxn=2e3+3;
using namespace std;
int size[maxn];
vector<pii>G[maxn];
ll dis[maxn],dp[maxn][maxn];
int n,k;
void dfs(int fa,int now){
size[now]=1;
for(int i=0;i<G[now].size();++i){
int to=G[now][i].first,v=G[now][i].second;
if(to==fa) continue;
dfs(now,to);
memset(dis,0,sizeof(dis));
int num1=min(k,size[now]),num2=min(k,size[to]);
for(int i=0;i<=num1;++i){
for(int j=0;j<=num2;++j){
if(i+j>k) break;
ll t=1ll*v*(j*(k-j)+(size[to]-j)*(n-size[to]-k+j));
dis[i+j]=max(dis[i+j],dp[now][i]+dp[to][j]+t);
}
}
for(int i=0;i<=k;++i) dp[now][i]=dis[i];
size[now]+=size[to];//先处理完了再染色,否则会重复计数
}
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>k;
for(int i=1;i<n;++i){
int a,b,c;
cin>>a>>b>>c;
G[a].push_back({b,c});
G[b].push_back({a,c});
}
dfs(-1,1);
cout<<dp[1][k]<<endl;
}
其余题目不写了~~