由于历史原因,neuq被camp无情拒绝
不过有一说一dls讲的课确实好。
H. Crystalfly
题目大意:给你一棵树,每个点都有一定数目的蝴蝶,但是当你经过父亲结点时,它的所有儿子结点的蝴蝶就会被惊动,在
t
i
(
1
≤
t
i
≤
3
)
t_i(1\leq t_i \leq 3)
ti(1≤ti≤3)秒消失,一开始你位于1号结点,问最大能够抓到的蝴蝶数目是多少。
分析:不难发现一个走到一个父亲结点时,最多可以拿到两个其儿子结点的蝴蝶数目,因为拿个一个结点再回来再去拿另一个此时已经到了三秒时间,而其它儿子结点的蝴蝶全都消失了,因此我们可以以此为状态进行dp。我们可以设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示对于以
i
i
i为根的子树(不包括
i
i
i)中选拿到
j
j
j个子结点蝴蝶捉到蝴蝶的最大数目,那么
f
[
i
]
[
0
]
=
∑
f
[
v
]
=
i
m
a
x
(
f
[
v
]
[
1
]
,
f
[
v
]
[
2
]
)
f
[
i
]
[
1
]
=
f
[
i
]
[
0
]
+
m
a
x
(
v
a
l
[
v
]
)
f
[
i
]
[
2
]
=
m
a
x
(
f
[
i
]
[
0
]
+
f
[
v
1
]
[
0
]
−
m
a
x
(
f
[
v
1
]
[
1
]
,
f
[
v
1
]
[
2
]
)
+
v
a
l
[
v
1
]
+
v
a
l
[
v
2
]
)
(
t
[
v
2
]
=
3
,
v
1
≠
v
2
)
f[i][0]=\sum_{f[v]=i} max(f[v][1],f[v][2]) \\ f[i][1]=f[i][0]+max(val[v]) \\ f[i][2]=max(f[i][0]+f[v_1][0]-max~(f[v_1][1],f[v_1][2])+val[v_1]+val[v_2]) (t[v_2]=3,v_1\neq v_2)
f[i][0]=∑f[v]=imax(f[v][1],f[v][2])f[i][1]=f[i][0]+max(val[v])f[i][2]=max(f[i][0]+f[v1][0]−max (f[v1][1],f[v1][2])+val[v1]+val[v2])(t[v2]=3,v1=v2)
对于前两个方程直接统计即可,第三个方程只需维护
f
[
v
1
]
[
0
]
−
m
a
x
(
f
[
v
1
]
[
1
]
,
f
[
v
1
]
[
2
]
)
+
v
a
l
[
v
1
]
f[v_1][0]−max(f[v_1][1],f[v_1][2])+val[v_1]
f[v1][0]−max(f[v1][1],f[v1][2])+val[v1]的最大值和次大值即可。最后答案即为
m
a
x
(
f
[
1
]
[
1
]
,
f
[
1
]
[
2
]
)
+
v
a
l
[
1
]
max(f[1][1],f[1][2])+val[1]
max(f[1][1],f[1][2])+val[1]
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define int long long
#define maxn 1000005
using namespace std;
int read()
{
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
x=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
struct edge{
int next,to;
}g[maxn<<1];
int val[maxn],num,last[maxn],n,t[maxn],f[maxn][3],ma[maxn],aa,bb;
void clean()
{
for(int i=1;i<=num;i++) last[i]=0,g[i]=(edge){0,0};
for(int i=1;i<=n;i++) f[i][0]=f[i][1]=f[i][2]=0,ma[i]=0;
num=0;
}
void add(int from,int to)
{
g[++num].next=last[from];
g[num].to=to;
last[from]=num;
}
void dfs(int x,int fa)
{
int tot=0,max1=-2e18,max2=-2e18,maxx=0,max1v,max2v,cnt=0;
for(int i=last[x];i;i=g[i].next)
{
int v=g[i].to;
if(v!=fa) {
cnt++;dfs(v,x);
ma[v]=max(f[v][1],f[v][2]);
tot+=ma[v];maxx=max(maxx,val[v]);
int c=f[v][0]-ma[v]+val[v];
if(c>max1){
max2v=max1v;max2=max1;
max1v=v;max1=c;
}
else if(c>max2) max2v=v,max2=c;
}
}
f[x][0]=tot;f[x][1]=tot+maxx;
if(cnt<=1) return;
for(int i=last[x];i;i=g[i].next)
{
int v=g[i].to;
if(v!=fa&&t[v]==3){
if(v!=max1v)
f[x][2]=max(f[x][2],tot+max1+val[v]);
else f[x][2]=max(f[x][2],tot+max2+val[v]);
}
}
}
void solve()
{
clean();n=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=1;i<=n;i++) t[i]=read();
for(int i=1;i<n;i++)
{
aa=read();bb=read();
add(aa,bb);add(bb,aa);
}
dfs(1,0);
printf("%lld\n",max(f[1][1],f[1][2])+val[1]);
}
signed main()
{
int t=read();
while(t--)
solve();
}
F. Towers
题目大意:给你一棵树,每个结点都有一个高度
h
i
h_i
hi,要求在一些结点上建塔,塔高为
w
i
w_i
wi,并且使得任意一个结点都存在一条包含该点的简单路径
x
−
>
y
x->y
x−>y使得
w
[
x
]
,
w
[
y
]
≥
h
[
i
]
w[x],w[y] \geq h[i]
w[x],w[y]≥h[i],求
∑
w
[
i
]
\sum w[i]
∑w[i]的最小值。
分析:首先对于根结点,我们把高度最高的结点设为根结点,这样我们对于非根结点只需要在其包含自身的子树中找到塔高最高的结点,并将其高度提升到大于等于这个结点的高度,这样对于每个非根结点就一定可以找到一条路径符合题意,对于根节点,只需要求出最大和次大的子树并把其高度分别提升至根节点的高度即可,这样我们对每个子树只需要维护其最高的塔高即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define clean(x) memset(x,0,sizeof(x))
#define maxn 1000005
#define int long long
using namespace std;
int read()
{
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
x=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
struct node{
int next,to;
}g[maxn<<1];
int n,h[maxn],num,aa,bb,maxx,root,last[maxn],f[maxn],sz[maxn],ans,pd,tot1,tot2;
void add(int from,int to)
{
g[++num].next=last[from];
g[num].to=to;
last[from]=num;
}
void dfs(int u,int fa)
{
sz[u]=1;
for(int i=last[u];i;i=g[i].next)
{
int v=g[i].to;
if(v!=fa)
{
dfs(v,u);
sz[u]+=sz[v];
f[u]=max(f[u],f[v]);
if(u==root){
if(f[v]>tot1){
tot2=tot1;
tot1=f[v];
}
else if(f[v]>tot2) tot2=f[v];
}
}
}
if(u==root)
ans+=h[u]-tot1+h[u]-tot2;
else
if(f[u]>=h[u]) return;
else {
ans+=h[u]-f[u];
f[u]=h[u];
}
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
h[i]=read();
if(h[i]>=maxx) {
maxx=h[i];
root=i;
}
}
for(int i=1;i<n;i++)
{
aa=read();bb=read();
add(aa,bb);add(bb,aa);
}
dfs(root,0);
cout<<ans;
return 0;
}
C. Paint
题目大意:给定一个序列,每次可以选择其中连续且数值相同的一段并将这一段数值全部变成任意一个数,最少进行多少次操作可以使得序列所有的数值都相同,求这个最小值。
分析:对于一段连续的区间,如果区间两侧的数相同,那么只需要操作一次即可让左右两个数加上该区间的数全都相同,因此我们可以考虑序列中最多可以找出多少左右配对的区间,因此我们设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示区间
[
i
,
j
]
[i,j]
[i,j]的最大匹配数目,那么
f
[
i
]
[
j
]
=
f
[
i
+
1
]
[
j
]
f
[
i
]
[
j
]
=
f
[
i
+
1
]
[
m
−
1
]
+
f
[
m
]
[
j
]
+
1
(
n
e
x
t
[
a
[
i
]
]
=
m
)
f[i][j]=f[i+1][j] \\ f[i][j]=f[i+1][m-1]+f[m][j]+1(next[a[i]]=m)
f[i][j]=f[i+1][j]f[i][j]=f[i+1][m−1]+f[m][j]+1(next[a[i]]=m)
我们只需要记录next[i]的位置即可,由于每个数出现此时不超过20次,时间复杂度为
O
(
20
n
2
)
O(20n^2)
O(20n2)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define clean(x) memset(x,0,sizeof(x))
#define maxn 5005
using namespace std;
int read()
{
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
x=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
int n,a[maxn],lt[maxn],nt[maxn],f[maxn][maxn];
void solve()
{
n=read();
for(int i=1;i<=n;i++) lt[i]=nt[i]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=0;
for(int i=1;i<=n;i++)
{
a[i]=read();
if(lt[a[i]]==0) lt[a[i]]=i;
else{
nt[lt[a[i]]]=i;
lt[a[i]]=i;
}
}
for(int i=2;i<=n;i++){
for(int l=1;l<=n-i+1;l++){
int r=l+i-1;
f[l][r]=max(f[l][r],f[l+1][r]);
int k=nt[l];
while(k&&k<=r){
f[l][r]=max(f[l][r],f[l+1][k]+f[k][r]+1);
k=nt[k];
}
// cout<<l<<" "<<r<<" "<<f[l][r]<<endl;
}
}
printf("%d\n",n-1-f[1][n]);
}
int main()
{
int t=read();
while(t--)
solve();
return 0;
}
C. Werewolves
题目大意:给你一棵树,每个点都有一个颜色,对于每一种颜色我们要求出所有的该颜色数目严格多于其它颜色的子树的数目。
分析:我们在计算某一种颜色之时,我们可以将该颜色的结点权值设为1,非该颜色结点权值设为-1,那么符合条件的子树的数目就是子树权值为正的方案数,这个可以通过树上背包方案数实现,但是问题就是时间复杂度,这样做如果颜色数目很多时间复杂度是
O
(
n
3
)
O(n^3)
O(n3),然而仔细分析一下会发现对于颜色很多的情况,每次背包非该颜色的结点会非常多,而该颜色结点会非常少,假设当前颜色数目为m,那么如果某个状态等于-m时,这个状态就对答案不可能有贡献了,因此我们只需要对区间
[
−
m
,
m
]
[-m,m]
[−m,m]之间的状态进行转移即可,因此时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define mod 998244353
#define int long long
#define clean(x) memset(x,0,sizeof(x))
#define maxn 3005
using namespace std;
int read()
{
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
x=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
struct edge{
int next,to;
}g[maxn<<1];
int n,num,last[maxn],aa,bb,ans,val[maxn],sz[maxn],s[maxn<<1],t[maxn<<1],f[maxn][2*maxn+100];
void add(int from,int to)
{
g[++num].next=last[from];
g[num].to=to;
last[from]=num;
}
void dfs(int x,int fa,int va,int m)
{
int c;sz[x]=1;
if(val[x]==va) c=1;
else c=-1;
f[x][c+m]=1;
for(int i=last[x];i;i=g[i].next)
{
int v=g[i].to;
if(v!=fa){
dfs(v,x,va,m);
sz[x]+=sz[v];
for(int j=max(-m,-sz[v]);j<=min(m,sz[v]);j++)
{
if(f[v][j+m]==0) continue;
if(j<0) for(int k=max(-m,-sz[x]+sz[v]);k<=min(m,sz[x]-sz[v]);k++)
{if(k+j<=m&&k+j>=-m) t[k+j+m]=(t[k+j+m]+f[x][k+m]*f[v][j+m])%mod;}
else for(int k=min(m,sz[x]-sz[v]);k>=max(-m,-sz[x]+sz[v]);k--)
if(k+j<=m&&k+j>=-m) t[k+j+m]=(t[k+j+m]+f[x][k+m]*f[v][j+m])%mod;
}
for(int j=-m;j<=m;j++) {
f[x][j+m]=(f[x][j+m]+t[j+m])%mod;
t[j+m]=0;f[v][j+m]=0;
}
}
}
for(int j=1;j<=m;j++) ans=(ans+f[x][j+m])%mod;
}
signed main()
{
//freopen("1.in","r",stdin);
n=read();
for(int i=1;i<=n;i++) {
val[i]=read();
s[val[i]]++;
}
for(int i=1;i<n;i++)
{
aa=read();bb=read();
add(aa,bb);
add(bb,aa);
}
for(int i=1;i<=n;i++){
if(s[i]==0) continue;
memset(f[1],0,sizeof(f[1]));
dfs(1,0,i,s[i]);
}
cout<<ans;
return 0;
}
P4766 [CERC2014]Outer space invaders
题目大意:N个外星人进攻,第
i
i
i个外星人在时间
a
i
a_i
ai出现,距离
d
i
d_i
di它必须在时间
b
i
b_i
bi前被消灭,你可以消灭与你的距离在
R
R
R以内的所有外星人(可以等于),同时它也会消耗
R
R
R单位的燃料电池。求摧毁所有外星人的最低成本(消耗多少燃料电池),同时保证自己的生命安全。
分析:我们每次都一定要通过一颗最大的炸弹消灭距离最远的外星人,并且可以枚举放置这个炸弹的时间,然后就可以分成左右两个区间再分别进行求解,我们可以用记忆化搜索来实现区间dp,设
d
p
[
l
]
[
r
]
dp[l][r]
dp[l][r]表示在l—r时间打死所有外星人花费的代价
d
p
[
l
]
[
r
]
=
m
i
n
(
d
p
[
l
]
[
k
−
1
]
+
a
[
i
d
]
.
d
+
d
p
[
k
+
1
]
[
r
]
)
(
k
>
=
a
[
i
d
]
.
l
t
,
k
<
=
a
[
i
d
]
.
r
t
)
dp[l][r]=min(dp[l][k-1]+a[id].d+dp[k+1][r]) ~~(k>=a[id].lt,k<=a[id].rt)
dp[l][r]=min(dp[l][k−1]+a[id].d+dp[k+1][r]) (k>=a[id].lt,k<=a[id].rt)
时间复杂度
O
(
n
3
)
O(n^3)
O(n3)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define clean(x) memset(x,0,sizeof(x))
#define maxn 605
using namespace std;
int read()
{
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
x=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
struct node{
int l,r,h;
}g[maxn];
int n,f[maxn][maxn],cnt,b[maxn*2];
int dfs(int l,int r)
{
if(l>r) return 0;
if(f[l][r]!=-1) return f[l][r];
int mpos=-1,mval=-1;
for(int i=1;i<=n;i++)
{
if(g[i].l<l||g[i].r>r) continue;
if(g[i].h>mval) mval=g[i].h,mpos=i;
}
if(mval==-1) return f[l][r]=0;
int ans=2e9;
for(int i=g[mpos].l;i<=g[mpos].r;i++)
{
ans=min(ans,dfs(l,i-1)+dfs(i+1,r)+mval);
}
f[l][r]=ans;
return f[l][r];
}
void solve()
{
n=read();cnt=0;
memset(f,-1,sizeof f);
for(int i=1;i<=n;i++)
{
g[i].l=read();g[i].r=read();g[i].h=read();
b[++cnt]=g[i].l;b[++cnt]=g[i].r;
}
sort(b+1,b+1+cnt);
int tott=unique(b+1,b+1+cnt)-b-1;
for(int i=1;i<=n;i++){
g[i].l=lower_bound(b+1,b+1+tott,g[i].l)-b;
g[i].r=lower_bound(b+1,b+1+tott,g[i].r)-b;
// cout<<g[i].l<<" "<<g[i].r<<endl;
}
printf("%d\n",dfs(1,tott));
}
int main()
{
int t=read();
while(t--)
solve();
return 0;
}