树形动归 专题

2010110916450924.png

直觉上觉得颜色数量不会很多。我就假定最后最多10种颜色。

假定一个root。

f[i][k]表示i为root的子树里i取k色,得到的最小值。

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< cstdio >
#define INF 1000000000
using namespace std;
struct node{
int y,next;
}E[
100001 ];


int cnt,edg[ 50001 ],n,x,y,ans,f[ 50001 ][ 11 ],NN;
void add_edge( int x, int y)
{
++ cnt;E[cnt].y = y;E[cnt].next = edg[x];edg[x] = cnt;
}
int dp( int i, int x, int last)
{
if (f[i][x] !=- 1 ) return f[i][x];

int Min = INF,tt;
tt
= x;
for ( int j = edg[i];j != 0 ;j = E[j].next)
if (E[j].y != last)
{
Min
= INF;
for ( int k = 1 ;k <= NN; ++ k) if (k != x)
if (dp(E[j].y,k,i) < Min) Min = dp(E[j].y,k,i);
tt
+= Min;
}

f[i][x]
= tt;
return tt;
}
int main()
{
freopen(
" GEMS.in " , " r " ,stdin);
freopen(
" GEMS.out " , " w " ,stdout);
NN
= 10 ;
scanf(
" %d " , & n);
for ( int i = 1 ;i < n; ++ i)
{
scanf(
" %d%d " , & x, & y);
add_edge(x,y);
add_edge(y,x);
}
for ( int i = 1 ;i <= n; ++ i) for ( int j = 1 ;j <= 100 ; ++ j) f[i][j] =- 1 ;
ans
= INF;
for ( int i = 1 ;i <= NN; ++ i)
if (dp( 1 ,i, 0 ) < ans) ans = dp( 1 ,i, 0 );
cout
<< ans << endl;
return 0 ;
}

数据生成器

【题目背景】

小明在做NOI2003练习赛的《幸福的老鼠》时觉得题目太简单了,于是对原题做了一些扩展:

l       将原题的N从20扩展到200000。

l       将原题经过一条街道需要的时间改为Ti(1 £ Ti £ 1000000000)分钟(i为街道的编号)。

l       增加了一个条件:小狗家Y离老鼠家X的距离小于等于大狗家Z离老鼠家X的距离。

即使这样,他仍然很快地做了出来。于是,小明打算做一些输入文件来测试他的程序。现在他已经生成了一些符合题意的图,不过为了增大测试数据的难度,他希望你能帮他选取一组X、Y、Z,使老鼠拿到礼物的时间尽可能地大。

【小明扩展的题目(注意,你并不需要解决此题)】

幸福的老鼠Jerry要过生日了,小狗大狗分别送了它一份生日礼物。现在Jerry打算从自己家X出发,先到小狗家Y(因为小狗家Y离老鼠家X的距离小于等于大狗家Z离老鼠家X的距离),再到大狗家Z,将两份礼物取回。

卡通城由N(3 £ N £ 200000)个居住点和N-1条连接居住点的双向街道组成,经过第i条街道需花费Ti(1 £ Ti £1000000000)分钟的时间。可以保证,任两个居住点间都存在通路

不妨设Jerry家在点X,小狗家在点Y,大狗家在点Z。现在,请你计算,Jerry最快需要耗费多长时间才能拿到生日礼物?

【任务描述】

定义:令|AB|表示卡通城中从A点走到B点需要的最少时间。

给出卡通城的地图,找到一组XYZ,使得: 

l       |XY|≤|XZ|

l       |XY|+|YZ|最大。

并求出此时|XY|+|YZ|的值。

【输入文件】

输入文件jerrygen.in第一行是两个整数N(3 £ N £ 200000)和MM=N-1),分别表示居住点总数和街道总数。从第2行开始到第N行,每行给出一条街道的信息。第i+1行包含整数UiViTi(1£Ui, Vi £ N,1 £ Ti £ 1000000000),表示街道i连接居住点UiVi,并且经过街道i需花费Ti分钟。

【输出文件】

输出文件jerrygen.out仅包含一个整数T,即|XY|+|YZ|的最大值。

【样例输入】

4 3

1 2 1

2 3 1

3 4 1

【样例输出】

4

先假定一个root。

分析一下实质上是求前三长路。fir[i],sec[i],thi[i]分别表示该树根子数下的k短。

from[i]在记录一下fir[i]是用那条路更新的。最后再dfs吧父亲也给考虑下去。

有很多这样先假定树根,少考虑父亲然后最后再线性dfs补充烤炉的题目。

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< cstdio >
#define LL long long
#define N 200001
using namespace std;
struct node{
LL y,z,next;
}E[N
* 2 ];

LL edg[N],cnt,x,y,z,ans,fir[N],sec[N],thi[N],from[N],vis[N],n,m;
void add_edge(LL x,LL y,LL z)
{
++ cnt;E[cnt].y = y;E[cnt].next = edg[x];edg[x] = cnt;E[cnt].z = z;
}
void dp(LL x,LL last,LL tot)
{
// if (vis[x]) return;
// vis[x]=1;
// ar[x]=tot;
LL v;
for (LL j = edg[x];j != 0 ;j = E[j].next)
if (last != E[j].y)
{
dp(E[j].y,x,tot
+ E[j].z);
v
= E[j].y;
if (fir[v] + E[j].z > fir[x]) {from[x] = v;thi[x] = sec[x];sec[x] = fir[x];fir[x] = fir[v] + E[j].z;}
else
if (fir[v] + E[j].z > sec[x]) {thi[x] = sec[x];sec[x] = fir[v] + E[j].z;}
else
if (fir[v] + E[j].z > thi[x]) {thi[x] = fir[v] + E[j].z;}
}
}
void dfs(LL x,LL last,LL tot)
{
if (tot > fir[x]) {from[x] = last;thi[x] = sec[x];sec[x] = fir[x];fir[x] = tot;}
else
if (tot > sec[x]) {thi[x] = sec[x];sec[x] = tot;}
else
if (tot > thi[x]) {thi[x] = tot;}
LL kk
= 0 ;
for (LL j = edg[x];j != 0 ;j = E[j].next)
if (last != E[j].y)
{
kk
= tot;
if (from[x] != E[j].y && fir[x] > kk) kk = fir[x];
if (from[x] == E[j].y && sec[x] > kk) kk = sec[x];
dfs(E[j].y,x,kk
+ E[j].z);
}

}
int main()
{
freopen(
" jerrygen.in " , " r " ,stdin);
freopen(
" jerrygen.out " , " w " ,stdout);
scanf(
" %d%d " , & n, & m);
for (LL i = 1 ;i <= m; ++ i)
{
scanf(
" %d%d%d " , & x, & y, & z);
add_edge(x,y,z);
add_edge(y,x,z);
}
dp(
1 , 0 , 0 );
dfs(
1 , 0 , 0 );
// for (LL i=1;i<=n;++i)
// printf("%d %d %d %d\n",i,fir[i],sec[i],thi[i]);
/*
for (LL i=1;i<=n;++i)
{
if (ar[i]>fir[i]) {thi[i]=sec[i];sec[i]=fir[i];fir[i]=ar[i];}
else
if (ar[i]>sec[i]) {thi[i]=sec[i];sec[i]=ar[i];}
else
if (ar[i]>thi[i]) {thi[i]=ar[i];}
}
*/
// for (LL i=1;i<=n;++i)
// printf("*(%d %d %d %d\n",i,fir[i],sec[i],thi[i]);
for (LL i = 1 ;i <= n; ++ i)
if (fir[i] + sec[i] * 2 + thi[i] > ans) ans = fir[i] + sec[i] * 2 + thi[i];
cout
<< ans << endl;
return 0 ;
}

Day 2

(7.18 14:00-19:30)

Tribe council

有一个部族有N个人,这里有一个他们的家族树,1是根节点表示父亲和儿子的关系。节点1是树根。他们要在神圣的山的西边进行一个奇特的会议,就坐在一排桌子旁边,如下图。每个人都尽可能的往前面的桌子坐,如果那个桌子上当前没有他的儿子或者父亲。

现在让你确定一个他们就坐的顺序,使得他们占的桌子数最多。

任务

求能占有的最多桌子数

输入文件 tribe.in

第一行是人数N

以下N-1行每行两个整数x,y,表示x是y的父亲

输出文件tribe.out

把答案输出到文件

数据范围

1<N<=100 000

30%的数据N<=1000

每张桌子有无限的容量

样例

tribe.in

tribe.out

5

1 4

3 2

1 3

3 5

3

解释

样例可以以这种顺序入座 2 4 1 5 3

  • 2 到桌1
  • 4 到桌1
  • 1 有孩子 (4) 在桌1,所以去桌2
  • 5 到桌1
  • 3 有2个孩子在桌1(2 and 5) 而且有父亲 (1)在桌2,所以去桌3.

要求一共能占据多少排,那么就是要使某一个人能坐到尽量靠后的排。在解决问题之前,我们要考虑这样几个问题。假如一个人能坐到第n排,那么他必然能坐到1到n排,因为他坐到第n排的时候,他的相邻点能占据1,2…n-1排,那么让占据1的先坐,然后到占据2的,……。只要他在适当的时候插入,就能坐到1至n排之中的任意一排。另外,如果一个人能坐到第n排,那么他的相邻点一定能分别占据1到n-1排,由于这个点之后才到会,所以之前,每个相邻点都是凭着去掉这个点后的某个子树的人际关系,坐到一个适当位置的。更确切的说,一个点能坐到第n排,按照一定顺序,他的相邻点p1、p2、p3…能坐到的最后排a1、a2、a3……满足a1>=1,a2>=2,a3>=3……。所以在得知一个点的所有相邻点能坐到的最大排后,坐到第一排的肯定用最小的,坐到第二排的肯定用次小的,我们按他们能坐到的最大排来排序,就能很容易的判断一个点能坐到第几排。

这题也是要考虑一个点的各个分叉,我们可以先假设树有一个根,我们先求一个点能凭借它的儿子坐到多少排。然后我们发现,我们没有考虑了一个点通向根的一个分叉,我们需要把漏掉的补上。假设P是Q的父亲节点,P能坐到的最大排已经求出。P要为Q服务,就必须将P的减掉Q这个分支后,再求它坐到的最大排,把P加入Q的相邻点,由于根节点已经完全考虑,我们从根节点往下,就能把所有点求出。-------集训队论文

其实这题跟上题一样都是先假定树根,然后再现不考虑父亲。

最后再dfs一遍补充考虑父亲。

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< cstdio >
#define INF 10000000000
#define N 100001
using namespace std;
struct node{
int y,next;
}E[N
* 2 ];

int edg[N],x,y,ans,n;
int sum[N][ 30 ];
int f[N],cnt;
void add_edge( int x, int y)
{
++ cnt;E[cnt].y = y;E[cnt].next = edg[x];edg[x] = cnt;
}
void dp( int x, int last)
{
for ( int j = edg[x];j != 0 ;j = E[j].next)
if (E[j].y != last)
{
dp(E[j].y,x);
++ sum[x][f[E[j].y ] ];
}
int tot = 0 ;
for ( int i = 1 ;i <= 20 ; ++ i)
{
tot
+= sum[x][i]; if (tot > i) tot = i;
}
f[x]
= tot + 1 ;
return ;
}
void dfs( int x, int last, int mm)
{

int tot = 0 ;

++ sum[x][mm];
for ( int i = 1 ;i <= 20 ; ++ i)
{
tot
+= sum[x][i]; if (tot > i) tot = i;
}
if (tot + 1 > ans) ans = tot + 1 ; // calc_ans


for ( int j = edg[x];j != 0 ;j = E[j].next)
if (E[j].y != last)
{
-- sum[x][ f[E[j].y ] ];

tot
= 0 ;
for ( int i = 1 ;i <= 20 ; ++ i)
{
tot
+= sum[x][i]; if (tot > i) tot = i;
}

dfs(E[j].y,x,tot
+ 1 );

++ sum[x][ f[E[j].y ] ];

}
}
int main()
{
freopen(
" tribe.in " , " r " ,stdin);
freopen(
" tribe.out " , " w " ,stdout);
scanf(
" %d " , & n);
for ( int i = 1 ;i < n; ++ i)
{
scanf(
" %d%d " , & x, & y);
add_edge(x,y);
add_edge(y,x);
}

ans
= 0 ;
dp(
1 , 0 );
for ( int i = 1 ;i <= n; ++ i) if (f[i] > ans) ans = f[i];
dfs(
1 , 0 , 0 );
cout
<< ans << endl;
return 0 ;
}

    选课

[问题描述]

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?

输入:

第一行有两个整数N,M用空格隔开。(1<=N<=300,1<=M<=160)

接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N,1<=si<=20)。

输出:

只有一行,选M门课程的最大得分。

样例:

输入:

7  4

2  2

0  1

0  4

2  1

7  1

7  6

2  2

输出:

13

徐持衡论文。。。。。

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< cstdio >
#define N 1000
using namespace std;
struct node{
int y,next;
} E[N
* 2 ];

int edg[N],ans,s[N],n,x,C,cnt;
int f[N][N];
void add_edge( int x, int y)
{
++ cnt;E[cnt].next = edg[x];edg[x] = cnt;E[cnt].y = y;
}
void dp( int u, int C)
{
if (C <= 0 ) return ;
int v;

for ( int j = edg[u];j != 0 ;j = E[j].next)
{
v
= E[j].y;
for ( int i = 0 ;i <= C; ++ i) f[v][i] = f[u][i];
dp(v,C
- 1 );
for ( int i = 0 ;i <= C - 1 ; ++ i)
if (f[v][i] + s[v] > f[u][i + 1 ]) f[u][i + 1 ] = f[v][i] + s[v];
}
}
int main()
{
freopen(
" choice.in " , " r " ,stdin);
freopen(
" choice.out " , " w " ,stdout);
scanf(
" %d%d " , & n, & C);
for ( int i = 1 ;i <= n; ++ i)
{
scanf(
" %d%d " , & x, & s[i]);
add_edge(x,i);
}
dp(
0 ,C + 1 );
for ( int i = 0 ;i <= C; ++ i) if (f[ 0 ][i] > ans) ans = f[ 0 ][i];
cout
<< ans << endl;
return 0 ;
}

转载于:https://www.cnblogs.com/LitIce/archive/2010/11/09/1872835.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值