没什么思路,不过n=10这30分可以试试。
此时的深搜是输出所有的组合数,能编得出吗,尝试了,代码如下。
#include <bits/stdc++.h>
#define maxn 1010
using namespace std;
int a[maxn],vis[maxn],n;
void dfs(int step,int len){
if(step==len+1){
for(int j=1;j<=len;j++)
printf("%d ",a[j]);
printf("\n");
return;
}
for(int i=1;i<=n;i++)
if(!vis[i]){
vis[i]=1;
a[step]=i;
dfs(step+1,len);
vis[i]=0;
}
}
int main(){
int i;
scanf("%d",&n);
for(i=1;i<=n;i++)
dfs(1,i);
return 0;
}
对应的输入输出数据如下:
输入:
3
输出:
1
2
3
1 2
1 3
2 1
2 3
3 1
3 2
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
看了效果,发现写成了输出所有排列数,马上改进,还是要输出所有组合数。
#include <bits/stdc++.h>
#define maxn 1010
using namespace std;
int a[maxn],n;
void dfs(int step,int len){
if(step==len+1){
for(int j=1;j<=len;j++)
printf("%d ",a[j]);
printf("\n");
return;
}
for(int i=a[step-1]+1;i<=n;i++){
a[step]=i;
dfs(step+1,len);
}
}
int main(){
int i;
scanf("%d",&n);
for(i=1;i<=n;i++)
dfs(1,i);
return 0;
}
对应的输入输出数据如下:
输入:
3
输出:
1
2
3
1 2
1 3
2 3
1 2 3
现在可以开始编写 对于 30% 的测试数据,满足n≤10 的情况了
1.枚举所有组合数的深搜30分
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 640KB | 2MS |
测试点2 | 答案正确 | 652KB | 2MS |
测试点3 | 答案正确 | 656KB | 2MS |
测试点4 | 运行超时 | 788KB | 995MS |
测试点5 | 运行超时 | 820KB | 998MS |
测试点6 | 运行超时 | 980KB | 998MS |
测试点7 | 运行错误 | 592KB | 2MS |
测试点8 | 运行错误 | 584KB | 1MS |
测试点9 | 运行错误 | 584KB | 1MS |
测试点10 | 运行错误 | 584KB | 1MS |
LOJ
LUOGU
该编码,比较有开创性的是,用邻接矩阵hate[][]描述两者憎恨关系。
#include <bits/stdc++.h>
#define maxn 1010
#define LL long long
using namespace std;
int a[maxn],n,hate[maxn][maxn];
LL b[maxn],mx;
void dfs(int step,int len){
if(step==len+1){
LL sum=0;
int i,j;
for(i=1;i<=len;i++){
int k=a[i];
for(j=1;j<i;j++)
if(hate[k][a[j]])//排查与之前选中骑士的关系
return;
sum+=b[k];
}
mx=max(mx,sum);
return;
}
for(int i=a[step-1]+1;i<=n;i++){
a[step]=i;
dfs(step+1,len);
}
}
int main(){
int i,v;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%lld%d",&b[i],&v);
hate[i][v]=hate[v][i]=1;
}
for(i=1;i<=n;i++)
dfs(1,i);
printf("%lld\n",mx);
return 0;
}
2.类01背包的树上DP10分
通过上述30分编码,发现,憎恨关系是树形关系,可以用邻接表来表示,这是才发现该题与树是有关系的。
样例中的憎恨关系如下图所示:
测试数据2
输入:
3
50 3
20 3
30 1
输出:
70
上述数据中的憎恨关系如下图:
要是有个节点数量多些的数据该有多好,这样更能发现规律。
突然有个想法,若是树形结构,该题就比较好解决,若是有回路,如样例,该题暂时没有办法,先解决树形结构。
v是u的子节点
f[u][0]+=max(f[v][1],f[v][0])
v选了,u必不能选。
v不选,u可选,可不选。
f[u][1]+=f[v][0];
u选了,v必不能选。
根怎么定,直觉告诉我,谁作根都可以。
预判,应该可以拿到50分。
编码过程中,突然发现,相互憎恨的去重,也挺麻烦。
那么退而求其次,为了去重,开一个vis[][]二维数组,那么数据处理降到 对于60% 的测试数据,满足n≤100
测试数据2 很快通过,提交
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 运行错误 | 8808KB | 5MS |
测试点2 | 答案正确 | 704KB | 2MS |
测试点3 | 运行错误 | 8848KB | 5MS |
测试点4 | 运行错误 | 9172KB | 10MS |
测试点5 | 运行错误 | 9248KB | 6MS |
测试点6 | 答案错误 | 1408KB | 2MS |
测试点7 | 运行错误 | 592KB | 1MS |
测试点8 | 运行错误 | 584KB | 1MS |
测试点9 | 运行错误 | 584KB | 1MS |
测试点10 | 运行错误 | 580KB | 1MS |
LOJ
LUOGU
10分代码如下:
#include <bits/stdc++.h>
#define maxn 1010
#define LL long long
using namespace std;
LL f[maxn][2];
int n;
int head[maxn],tot,vis[maxn][maxn];
struct node{
int to,next;
}e[maxn<<1];
void add(int u,int v){
tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void dfs(int u,int fa){
int i,v;
for(i=head[u];i;i=e[i].next){
v=e[i].to;
if(v==fa)continue;
dfs(v,u);
f[u][0]+=max(f[v][1],f[v][0]);
f[u][1]+=f[v][0];
}
}
void init(){
int u,v;
scanf("%d",&n);
for(u=1;u<=n;u++){
scanf("%lld%d",&f[u][1],&v);
if(!vis[u][v]&&!vis[v][u]){
add(u,v),add(v,u);
vis[u][v]=vis[v][u]=1;
}
}
}
int main(){
init();
dfs(1,-1);
printf("%lld\n",max(f[1][0],f[1][1]));
return 0;
}
3.基环树
如果它不是有n条边,而是n-1条边的话,它就是一棵树,也就是 没有上司的舞会。
现在只多了一条边,那它就是基环树。相当于多了一个环,其他的结构没有很大的变化。
组成的是基环树森林而非单棵基环树
有向图 避免了 无向图中出现的重边。
所以 考虑这个有向图
我们把x所讨厌的人y设为x的父亲节点(理由是:父节点只有一个,父节点的子节点有多个)
题中样例对应的有向图
每个联通块内有且只有一个简单环
这样 我们考虑把每个联通块的环上删一条边
这样它必然构成树
然后要注意
删掉的边所连接的两点x,y
是不能同时选的
所以我们分别强制x,y其中一个点不选,这个一定不选的f[][1]=-maxn.
删边,并不是真正意义上的删边,是通过节点值的设置,不选该点。
难点:为什么两遍深搜结果取最大值是正确的(强制不选某点正确性证明)?
证明如下:
x存在选,不选两种可能。y存在选,不选两种可能。
组合后有4种可能。
x选,y选
x选,y不选
x不选,y选
x不选,y不选
因x,y两节点是互斥的,只有3种可能的组合是正确的。
x选,y不选
x不选,y选
x不选,y不选
x作为根,强制不选,那么包含2种可能的组合。
x不选,y选
x不选,y不选
y作为根,强制不选,那么包含2种可能的组合。
x选,y不选
x不选,y不选
将 x作为根,强制不选,y作为根,强制不选,合并后,包含3种可能的组合。
x选,y选
x不选,y不选
x不选,y不选
与 因x,y两节点是互斥的,只有3种可能的组合是正确的。完全匹配。
x选,y不选
x不选,y选
x不选,y不选
故在删边过程中,分别强制设置两个端点不选,结果是正确的。
样例的删边模拟过程如下:
根节点root=3,根节点的父节点fa[root]=1,删除1->3这条边
选节点3作为整棵树的根,强制节点3不能选(遍历到节点3时,设置f[3][1]=-maxn,注意设置一个很大的负数)。
f[2][0]=0,f[2][1]=20
f[1][0]=0,f[1][1]=10
f[3][0]=0,f[3][1]=-maxn
f[1][0]+=max(f[3][0],f[3][1]),f[1][0]=0
f[1][1]+=f[3][0],f[1][1]=10
f[2][0]+=max(f[1][0],f[1][1]),f[2][0]=10
f[2][1]+=f[[1][0],f[2][1]=20
f[3][0]+=max(f[2][0],f[2][1]),f[3][0]=20
f[3][1]+=f[2][0],f[3][1]=-maxn
t=0
t=max(f[3][0],f[3][1])
t=20
选节点1作为整棵树的根,强制节点1不能选(遍历到节点1时,设置f[1][1]=-maxn,注意设置一个很大的负数)。
f[3][0]=0,f[3][1]=30
f[2][0]=0,f[2][1]=20
f[1][0]=0,f[1][1]=-maxn
f[2][0]+=max(f[1][0],f[1][1]),f[2][0]=0
f[2][1]+=f[1][0],f[2][1]=20
f[3][0]+=max(f[2][0],f[2][1]),f[3][0]=20
f[3][1]+=f[[2][0],f[3][1]=30
f[1][0]+=max(f[3][0],f[3][1]),f[1][0]=30
f[1][1]+=f[3][0],f[1][1]=-maxn
t=max(t,max(f[1][0],f[1][1])),t=30
对新树跑DP
显然相邻的点是不能选的
所以得到状态转移方程:
用f[i][0/1]表示以i为根节点的子树选i/不选i所能获得的最大价值
则 f[i][0]=sigema(max(f[son][0],f[son][1])); for each son of i
f[i][1]=sigema(f[son][0]); for each son of i
ybt
通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 644KB | 1MS |
测试点2 | 答案正确 | 664KB | 2MS |
测试点3 | 答案正确 | 648KB | 2MS |
测试点4 | 答案正确 | 656KB | 2MS |
测试点5 | 答案正确 | 652KB | 2MS |
测试点6 | 答案正确 | 660KB | 2MS |
测试点7 | 答案正确 | 804KB | 2MS |
测试点8 | 答案正确 | 1164KB | 4MS |
测试点9 | 答案正确 | 15956KB | 129MS |
测试点10 | 答案正确 | 50868KB | 539MS |
LOJ
LUOGU
基环树有向图找环删边100分代码如下:
#include <bits/stdc++.h>
#define maxn 1000010
#define LL long long
using namespace std;
int n,head[maxn],tot,fa[maxn],vis[maxn],save;
LL f[maxn][2],mx,c[maxn];
int root;
struct node{
int to,next;
}e[maxn<<1];
void add(int u,int v){
tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void init(){
int v,u;
scanf("%d",&n);
for(v=1;v<=n;v++){
scanf("%lld%d",&c[v],&u);
add(u,v);
fa[v]=u;
}
}
void dfs(int u){
int i,v;
f[u][1]=c[u],f[u][0]=0;//两次dfs对应的初始化
for(i=head[u];i;i=e[i].next){
v=e[i].to;
vis[v]=1;
if(v==root){
f[v][1]=-maxn;
continue;
}
dfs(v);
f[u][1]+=f[v][0];
f[u][0]+=max(f[v][0],f[v][1]);
}
}
void find_circle(int x){
LL t=0;
vis[x]=1;
root=x;
while(!vis[fa[root]]){
root=fa[root];
vis[root]=1;
}
//将root与fa[root]连线隔断。
dfs(root);
t=max(f[root][0],f[root][1]);
root=fa[root];
dfs(root);
t=max(t,max(f[root][0],f[root][1]));
mx+=t;//基环树森林处理
}
int main(){
init();
for(int i=1;i<=n;i++)
if(!vis[i])//基环树森林处理
find_circle(i);
printf("%lld\n",mx);
return 0;
}