被自己之前的代码丑哭了:(
没有上司的舞会&树上求和
给出一个 n 个结点的树,每个结点都有一个权值 a ,请你选择一些结点,使得被选结点的权值和最大,且被选结点之间不能存在父子关系,即选了父亲不能选儿子,选了儿子不能选父亲。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1000010;
int n,tot,f[N][2],ans,head[N],leader[N];
bool vis[N];
struct edge{int to,next;}e[N<<1];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void add_edge(int u,int v)
{
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int u)
{
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v])
{
dfs(v);
f[u][1]+=f[v][0];
f[u][0]+=max(f[v][0],f[v][1]);
}
}
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) f[i][1]=read();
for(int i=1;i<n;i++)
{
int v=read();
int u=read();
leader[v]=1;
add_edge(u,v);
}
for(int i=1;i<=n;i++)
{
if(leader[i]==0)
{
dfs(i);
printf("%lld",max(f[i][0],f[i][1]));
return 0;
}
}
return 0;
}
结点覆盖
给出一棵有根树,要求选出一些结点。若选中某个结点,则它本身、它的父亲结点和儿子结点被覆盖。选出第 i 个结点需要一定的花费 ,求覆盖这棵树所需的最小花费。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100010;
int n,a[N],tot,head[N],f[N][3];
struct edge{int to,next;}e[N<<1];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void add_edge(int u,int v)
{
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int u,int fa)
{
f[u][0]=a[u];
bool flag=0;
int res=1e18;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
dfs(v,u);
f[u][0]+=min(f[v][0],min(f[v][1],f[v][2]));
f[u][2]+=min(f[v][0],f[v][1]);
if(f[v][0]<f[v][1]) flag=1;
else res=min(res,f[v][0]-f[v][1]);
f[u][1]+=min(f[v][0],f[v][1]);
}
if(!flag) f[u][1]+=res;
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
int u=read();
a[i]=read();
int m=read();
for(int j=1;j<=m;j++)
{
int v=read();
add_edge(u,v);
add_edge(v,u);
}
}
dfs(1,0);
printf("%lld",min(f[1][0],f[1][1]));
return 0;
}
最长距离
给出一个以 1 为根的 n 个结点的树,树边有权值,求出每个结点与相距最远结点间的距离 。
dfs1先只向下找,dfs2去取向下/先向上再向下的最大值,就是要特判一下u是否本身就在父结点的最长子链中。
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct edge{int to,next,w;}e[10010];
int n,m,ans[10010],head[10010],f[10010][3];
void add_edge(int u,int v,int w)
{
e[++m].to=v;
e[m].w=w;
e[m].next=head[u];
head[u]=m;
}
void dfs1(int u)
{
int maxx1,maxx2;
maxx1=maxx2=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
dfs1(v);
if(f[v][0]+e[i].w>maxx1)
{
ans[u]=v;
maxx2=maxx1;
maxx1=f[v][0]+e[i].w;
}
else if(f[v][0]+e[i].w>maxx2) maxx2=f[v][0]+e[i].w;
}
f[u][0]=maxx1;
f[u][1]=maxx2;
}
void dfs2(int u)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(ans[u]==v) f[v][2]=max(f[u][1],f[u][2])+e[i].w;
else f[v][2]=max(f[u][0],f[u][2])+e[i].w;
dfs2(v);
}
}
signed main()
{
while(scanf("%d",&n)!=EOF)
{
memset(head,0,sizeof head);
m=f[1][0]=f[1][1]=f[1][2]=0;
for(int i=2;i<=n;i++)
{
int fa,w;
scanf("%d%d",&fa,&w);
add_edge(fa,i,w);
f[i][0]=f[i][1]=f[i][1]=0;
}
dfs1(1);dfs2(1);
for(int i=1;i<=n;i++) printf("%d\n",max(f[i][0],f[i][2]));
}
return 0;
}
选课方案
在大学里,每个学生为了达到一定的学分,必须从很多课程里选择一些课程来学习,有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a ,才能学习课程 b )。一个学生要从这些课程里选择 m 门课程学习,问他能获得的最大学分是多少?
有一个点:森林无疑是难搞的,但是可以假设有一个点是所有根节点的父亲,那就好办了许多。 f[i][j] 表示对于节点 i , 一共选择 j 门课所能得到的最大学分(包括他本身)。选择他的所有子节点的前提是一定要选择这个节点,所 f[i][1] 一定等于 i 节点的权值
在枚举 j 的时候需要注意:由于 f[u][j] 由 f[u][j - k] 更新得到,那么就需要保证更新的时候 f[u][j - k] 没有被更新,由于 k 是一个正整数,所以我们倒序枚举 j 即可
#include<bits/stdc++.h>
using namespace std;
int n,m,f[1010][1010],head[100010],cnt;
struct edge{int u,v; }e[100010];
void add(int from,int to)
{
e[++cnt].v=head[from];
e[cnt].u=to;
head[from]=cnt;
}
void dp(int now)
{
for(int i=head[now];i;i=e[i].v)
{
int go=e[i].u;
dp(go);
for(int j=m+1;j>=1;j--)
for(int k=0;k<j;k++)
f[now][j]=max(f[now][j],f[go][k]+f[now][j-k]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int fa;
scanf("%d%d",&fa,&f[i][1]);
add(fa,i);
}
dp(0);
printf("%d",f[0][m+1]);
return 0;
}
路径求和
给出一棵树,求出所有至少一个端点为叶节点的有向路径的权值和的和。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+10;
int n,m,root,tot,ans,cnt;
int head[N],du[N],son[N],num[N];
struct edge{int to,next,w;}e[N*2];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void add_edge(int u,int v,int w)
{
e[++tot].to=v;
e[tot].w=w;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int u,int fa)
{
num[u]=1;
if(du[u]==1) cnt++,son[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
dfs(v,u);
son[u]+=son[v];
num[u]+=num[v];
}
}
void find(int u,int fa)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
find(v,u);
ans+=e[i].w*(son[v]*(n-num[v])+num[v]*(cnt-son[v]));
}
}
signed main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u,v,w;
w=read(),u=read(),v=read();
du[u]++;du[v]++;
add_edge(u,v,w);
add_edge(v,u,w);
}
dfs(1,0);find(1,0);
printf("%lld",ans);
return 0;
}
树上移动
在给定的树上:
一个人从 s 出发,求经过所有点的最短长度。
两个人从 s 出发,走的路径没有限制,求经过所有点的最短长度。
#include<bits/stdc++.h>
using namespace std;
struct edge{int to,next,w;}e[500010];
int n,m,sum,tot,f1[100010],f2[100010],head[100010];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void add_edge(int u,int v,int w)
{
e[++tot].to=v;
e[tot].w=w;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int u,int fa)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
dfs(v,u);
if(f1[v]+e[i].w>f1[u])
{
f2[u]=f1[u];
f1[u]=f1[v]+e[i].w;
}
else if(f1[v]+e[i].w>f2[u]) f2[u]=f1[v]+e[i].w;
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<n;i++)
{
int u,v,w;
u=read(),v=read(),w=read();
add_edge(u,v,w);
add_edge(v,u,w);
sum+=w;
}
dfs(m,0);
printf("%d\n",sum*2-f1[m]);
int maxx=0;
for(int i=1;i<=n;i++)
{
f1[i]+=f2[i];
maxx=max(maxx,f1[i]);
}
printf("%d",sum*2-maxx);
return 0;
}
块的计数
给定一棵 n 个节点的树,每个点有个喜欢程度,要求选一个联通块,并且这个联通块包含最大的喜欢程度的方案数,输出的方案数对 998244353 取模。
一个点也是联通块!!!正难则反,用联通块数减去不含最大值的联通块数,递归联通块数的时候用乘法原理就好。而且不保证点权为正,初始值赋0的话只有30pts/orz
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100010;
const int mod=998244353;
int n,tot,mx,head[N],a[N],f[N],g[N],sum,ans;
struct edge{int to,next;}e[N<<1];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void add_edge(int u,int v)
{
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int u,int fa)
{
g[u]=1;
if(a[u]==mx) f[u]=0;
else f[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
dfs(v,u);
f[u]=f[u]*(f[v]+1)%mod;
g[u]=g[u]*(g[v]+1)%mod;
}
sum=(sum+g[u])%mod;
ans=(ans+f[u])%mod;
}
signed main()
{
n=read();mx=-1e18;
for(int i=1;i<=n;i++)
{
a[i]=read();
mx=max(mx,a[i]);
}
for(int i=1;i<n;i++)
{
int u=read();
int v=read();
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
printf("%lld",(sum+mod-ans)%mod);
return 0;
}
权值统计
给出一个 n 个结点的无根树以及每个结点的权值,求出树的每一条路径的权值积的和,单独的一个结点也算作一条路径。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=10086;
int n,ans,a[100010],f[100010],head[100010],tot;
struct edge{int to,next;}e[500010];
void add_edge(int u,int v)
{
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int u,int fa)
{
int sum,tot;
sum=tot=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
dfs(v,u);
sum+=f[v];
tot+=f[v]*f[v];
}
f[u]=(sum+1)*a[u]%mod;
ans+=f[u]+(sum*sum-tot)*a[u]/2%mod;
ans%=mod;
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i< n;i++)
{
int u,v;
scanf("%lld%lld",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
printf("%lld",ans);
return 0;
}
树的合并
给定两棵树,则总共有 n*m 种方案把这两棵树通过加一条边连成一棵树,那这 n*m 棵树的直径大小之和是多少呢?树的直径指的是树上的最长简单路径。