[ZOJ1030 Farmland]【狗狗40题】【计算几何】【绕圈圈】【判点是否在多边形内】

【题目大意】

给定一个简单平面无向图。n<=200。每个点给出坐标

问图中包含边数为k的圈有多少个。

这些圈要满足:

1、面积>0

2、不能有其它的点或者边在圈内

【算法分析】

和大妈题#3的area(ZOJ2361)差不多。

先枚举起始边,然后对于路径中最后的两个点,x,z,构成向量x->z,然后再在与x相连的点集里找一个点y,满足x->z逆时针绕到x->y所夹的角最大。

然后就这样绕,注意每条边x->y只能走一次。因为从x->y绕多一遍只能绕出之前绕过的圈。方向不同(y->x)分开看待。

当我们找到一开始那个点时,实际就找到了圈了。

但是我们可能顺时针算了这个圈一次,逆时针又算了一次。而实际上,如果走逆时针走重复的话,必然和本次的起始点是一样的。

于是每次加答案时只需要判判把整条路径逆过来有没有出现过就行了。(我直接set < vector

然后再枚举看圈圈内有没有其它的点在。

这个要用到判点是否在多边形内。

基本上就这样。

【其它】

好像无论多暴力都0MS。

2Y。一开始没有注意到可能有杂点可能在多边形

T_T,好长好长

【CODE】

#include

#include

#include

#include

#include

#include

#include

#include

using namespace std;

const int N=256;

const double eps=1e-7;

const double INF=1e50;

const double pi=acos(-1);

 

int sign(double x){

    if (x<-eps) return -1;

    if (x> eps) return  1;

    return 0;

}

 

struct TPoint {double x,y;};

 

TPoint operator – (TPoint A,TPoint B){

    TPoint ret;

    ret.x=A.x-B.x;

    ret.y=A.y-B.y;

    return ret;

}

 

double operator ^ (TPoint A,TPoint B){

    return A.x*B.y-A.y*B.x;

}

 

double operator * (TPoint A,TPoint B){

    return A.x*B.x+A.y*B.y;

}

 

double Length(TPoint v){

    return sqrt(v.x*v.x+v.y*v.y);

}

 

double Get_Angle(TPoint v1,TPoint v2){

    return acos(v1*v2/Length(v1)/Length(v2));

}

 

bool On_Seg(TPoint p,TPoint A,TPoint B){

    if (sign((p-A)^(B-A))!=0) return false;

    if (min(A.x,B.x)<=p.x+eps && p.x<=max(A.x,B.x)+eps

    &&  min(A.y,B.y)<=p.y+eps && p.y<=max(A.y,B.y)+eps)

      return true;

    return false;

}

 

bool Seg_Cross(TPoint p1,TPoint p2,TPoint p3,TPoint p4){

    return

    sign( ( (p2-p1) ^ (p3-p1) ) * ( (p2-p1) ^ (p4-p1) ) ) <= 0 &&

    sign( ( (p3-p4) ^ (p1-p4) ) * ( (p3-p4) ^ (p2-p4) ) ) <= 0; 

}

 

set < pair

set < vector

vector

vector

vector

TPoint P[N];

int Tc,n;

int Need;

int Ans;

int v[N];

 

bool Inside(TPoint p){

    int cnt=0;

    TPoint oth;

    oth.x=INF;

    oth.y=p.y;

    for (int i=0;i

        if ( On_Seg(p,P[Path[i]],P[Path[i+1]]) ) return false;

    for (int i=0;i

        TPoint A=P[Path[i]];

        TPoint B=P[Path[i+1]];

        if (A.y

        if (sign(A.y-B.y)==0) continue;

        if (sign(p.y-A.y)==0) continue;

        if (Seg_Cross(p,oth,A,B))

            cnt++;

    }

    return cnt&1;

}

 

int check(){

    if (Path.size()<4) return 0;

    T_path.clear();

    for (int i=Path.size()-1;i>=0;i–)

      T_path.push_back(Path[i]);

    if (Path_Hash.count(T_path)) return 0;

    for (int i=1;i<=n;i++) v[i]=0;

    for (int i=0;i

      v[Path[i]]=1;

    for (int i=1;i<=n;i++)

      if (!v[i])

        if (Inside(P[i])) return 0;

    return 1;

}

 

void dfs(int st,int z,int x,int Len){

    if (Len>Need) return;

    if (x==st){

        if (Len==Need && check()){

            Ans++;

            Path_Hash.insert(Path);

        }

        return;

    }

    int best=-1;

    double best_angle=-INF;

    for (int i=0;i

        int y=G[x][i];

        int s=sign( (P[x]-P[z])^(P[y]-P[z]) );

        double angle=0;

        if (!( s==0 && sign( (P[x]-P[z])*(P[y]-P[x]) )<0 )){

            if (s < 0){

                angle=Get_Angle(P[z]-P[x],P[y]-P[x]);

            }

            if (s > 0){

                angle=2*pi-Get_Angle(P[z]-P[x],P[y]-P[x]);

            }

            if (s == 0){

                angle=pi;

            }

            if (angle>best_angle){

                best_angle=angle;

                best=y;

            }

        }

    }

    if (best==-1) return;

    int y=best;

    if (Hash.count(make_pair(x,y))) return;

    Hash.insert(make_pair(x,y));

    Path.push_back(y);

    dfs(st,x,y,Len+1);

}

 

void solve(){

    Hash.clear();

    Path_Hash.clear();

    Ans=0;

    int i,j;

    for (i=1;i<=n;i++){

        for (j=0;j

            int x=i;

            int y=G[i][j];

            if (Hash.count(make_pair(x,y))) continue;

            Hash.insert(make_pair(x,y));

            Path.clear();

            Path.push_back(x);

            Path.push_back(y);

            dfs(x,x,y,1);

        }

    }

}

 

int main(){

    scanf("%d",&Tc);

    while (Tc–){

        scanf("%d",&n);

        for (int i=0;i<=n;i++) G[i].clear();

        for (int i=0;i

            int x,num;

            scanf("%d",&x);

            scanf("%lf%lf%d",&P[x].x,&P[x].y,&num);

            for (int j=0;j

                int y;

                scanf("%d",&y);

                G[x].push_back(y);

            }

        }

        scanf("%d",&Need);

        solve();

        printf("%dn",Ans);

    }

}

【转】生命中的最后一天

    前些日子惊闻dsh的噩耗,是在网友的BLOG上看到的。当时我就笑了,怎么可能呢,愚人节还没过完么。不过仔细想想貌似有个把月没联系了, CALL之,但电话那头已经关机了。这时我就笑不出来了。后来从他同学那里得到消息,确实是几个月前查出肝癌晚期,几天前走了。还是不敢相信,精力如此旺盛的人,居然说走就走了。天妒奇才,尚未扬名立万而先逝,甚至连一篇讣告都没有。

 

       dsh生长于单亲家庭,母亲做项目,经常应酬,也许是这样塑造了他喜欢宁静、独处的性格。不得不承认,他是一个天才,真正的天才。初中就在理科方面显出了过人的天份。升入高中后,由于不喜欢教条的科目,他几乎没上过课。直到高考时他才有点“后悔”,但拥有过人天份的他依然不费吹灰之力就考上了某重点一本大学。

 

我们往往被理想的世界欺骗,直到亲自去做的时候才理解现实。

 

    上了几年大学才明白为什么要考北大清华,可即使你在北大清华你也会这么认为:资源总是太少、视野还是太窄、时间永远不够。因为你本身就是个跟时间赛跑的人。

 

    上了大学后的他依然故我。每天研究最艰深的算法题,每个stats上都能看到dsh的大号小号。软件学院的他没有在一开始就得到教练的亲睐。甚至软件学院的老师把他从寝室拉出来告诉他,做软件靠的是拉项目和各种应酬。他不喜欢每天上课还要刷指纹的禁锢,看自己喜欢的论文,啃那些别人永远看不懂的书。

 

你可以控制一个人的行为,但永远不能控制一个人的思想。

 

    母亲来看你,给你带了很多零食,可你狠心的将它们全都扔了下去;亲人喊你吃饭,你却依然待在电脑前写程序;妹妹跟你同校,但你却不知道小盆友们需要你的照顾。你像自私的葛朗台吝啬你的每一分情感,像一个极端自私的人。你说你要去美国,你说你要像先贤一样一辈子单身…

 

    还记得那时一起刷题,一起讨论,一起看《金田一少年事件簿》《最后的武士》,仿佛就是昨天。那年暑假,我们一起coding,你总是能想到别人想不到的算法,甚至连命题人都从来没有想到(比如北京赛区的那题destory bus station。你常常跟我提起年少时的经历使你讨厌数学,现在后悔当时没有好好学。后来有天你很激动地告诉我你做出了calculate trees那题我就知道你已经突破了内心的魔障。还记得那天夜里大家讨论各自的追求,你说了一句让我们震惊的话。是的,我从来没想过一个从来没有失败过的人是什么样的。这一刻我才真正认识到一个追求永恒不败的境界的人。

 

    你不是为奖牌而ACM。现在许多ACMer认为有了奖牌就等于有了好工作,或者保研的资本。你参加比赛从来都是以切掉最难的别人都做不出来的那道题为目标,因此你也丢了夺金的机会。大家做ACM的初衷是什么?对菜鸟来说是学习一些算法,对牛儿来说是领悟算法背后的哲学思想,对大神来说是去赛场show一下。对自己来说,学到东西、玩得快乐才是最重要的。在我看来,你仿佛天生就是为了解决类似于NP!=P这样终极问题的人。

 

    几个月前,我们聊了聊近况,你说你在啃傅立叶,你说你有看不完的论文,你还跟我说了许多出国留学的好处。你即将踏上你的留学之旅—纽约大学。纽约,多么美丽的城市,那里是全世界最繁华的城市之一、那里有华尔街、那里有全世界的智者贤才、那里有自由女神。据说你就是那时候查出的肝癌晚期,但你依然强势。你把QQ签名改成了“申请高峰期,请理解下”,我知道你又埋书堆了,就没敢打扰。不久前,你又在后面加了一句“看样子必须说再见了,我会想你们的”。我还以为你启程去美国了,却没想到这竟是最后的诀别!

 

一个人只有当他获得应有的社会地位时,他的才能发挥出来。

 

    你说我贪玩,我说你太偏执。现在我终于理解了你的偏执。各种不幸的经历让你讨厌命运之神。你知道你还在跟死神赛跑,但是你有你的梦想,你知道片刻都不能耽误。所以你很果断地行动,即使是高烧40度依然在月赛冲进前十。你的灵魂或许已经去大洋彼岸继续追逐你的大师之路了吧。

 

只有勇于面对死亡仍然不放弃梦想的人才是真正的武士,这就是武士道的精神。

 

    斯人去矣,群里常有只言片语的惋惜,大多数人只知道huicpc035。但他们不知道,你不喜欢这个名字,所以朋友们都叫你dsh,因为这是你的本名,是真正的自己。我们不应该只在记忆的碎片中怀念。故以此文:纪念那个逝去的友谊,纪念一个永恒的精神,纪念一个不灭的梦想,纪念一个勇于和命运抗争的斗士—杜思瀚。

 

*把每一天都当成生命中的最后一天,你就会轻松自在。

*每天早上洗脸时,请在镜子前问自己:如果今天是此生最后一日,我今天要干些什么?

*永远不要放弃梦想,即使是生命中的最后一秒。

*珍惜生命,珍惜现在,因为它可以很短暂,它离开无预兆。

 

 

 

 

Codeforces Beta Round #69 (Div. 1 Only) 【E题还是不会】

【A. Heroes】

就是一个DFS枚举,然后模拟。

最坏时间3^7 * 7^2

 

【B. Falling Anvils】

【题目大意】

给定一个方程。然后p的范围是[0,a],q的范围是[-b,b]。

p,q在各自区间上等概率分布。

问这个方程有解的概率。

【算法分析】

就是求p-4q>=0的概率。

然后把p看成x,q看成y。然后画出对应的平面图形分类讨论一下即可。高中数学课本的例题阿

代码就这样

        if (fabs(b)<1e-9) ret=1;

        else

        if (a

            ret=(2*b+a/4)/(4*b);

        }

        else{

            ret=1-b/a;

        }

注意b=0的情况。被坑了一次。

 

 

【C. Beavermuncher-0xFF】

【题目大意】

给定一棵有根树,每个点上有w[i]只鱼(其实题目是海狸= =),一开始你在根上。你每走一条边x->y,那么y上要少一条鱼。如果y上没鱼,那么不能走边x->y。现在要求你结束时依然需要站在根处,问你最多能干掉多少条鱼(即最多能走多少步)。

n<=10^5

【算法分析】

首先很容易发现我们可以把走出去的路线分解为很多圈圈,每个圈圈只包含两个点。

于是我们可通过点来划分路径。

考虑一棵以p为根的子树

我们可以以p为分界点把路径分隔开。

那么p下面怎么走和上面无关,只要剩下一条鱼在p上,就足够把p这个子树拼接到fa上。

我们考虑p上的鱼消失的两种方式。

A、从儿子走回来

B、从fa走下来

如果能选A,我们肯定尽量选A。因为如果选了B,那么必须要走回fa那儿去,那么就要同时消耗fa上鱼的数目,这就会影响以后的决策,使得后面取得的鱼变少。

于是这个贪心算法就很显然了。令F[i]表示以i为根,绕一圈回到i最多能吃到多少鱼。(如果i不为根,那么要求最后i上剩的鱼>=1,否则>=0)。

然后每次将自己的儿子按F[i]排个序。然后拼接就好了。

如果拼接完还有在i上的鱼多出来,那么就找个儿子卡能不能抵消。最后输出F[i]即可。

附代码

#include

#include

#include

#include

#include

using namespace std;

const int N=100005;

const int oo=0x80000000;

int n,S;

int w[N];

int fa[N];

int Q[N];

long long F[N];

vector

 

void bfs(){

    int head=0,tail=1;

    Q[1]=S;

    fa[S]=0;

    while (head!=tail){

        head++;

        int x=Q[head];

        int L=G[x].size();

        for (int i=0;i

            int y=G[x][i];

            if (fa[x]!=y){

                fa[y]=x;

                Q[++tail]=y;

            }

        }

    }

}

 

bool cmp(int i,int j){

    return F[i]>F[j];

}

 

void solve(){

    for (int i=1;i<=n;i++) F[i]=oo;

    for (int j=n;j>=1;j–){

        int low_limit= (j==1) ? 0 : 1;

        int x=Q[j];

        int L=G[x].size();

        F[x]=0;

        sort(G[x].begin(),G[x].end(),cmp);

        for (int i=0;i

            int y=G[x][i];

            if (fa[x]!=y){

                if (w[x]>low_limit && w[y]>0){

                    F[x]+=F[y]+2;

                    w[x]–;

                    w[y]–;

                }

            }

        }

        for (int i=0;i

            int y=G[x][i];

            if (fa[x]!=y){

                if (w[x]>low_limit && w[y]>0){

                    int delta=min(w[x]-low_limit,w[y]);

                    F[x]+=delta*2;

                    w[x]-=delta;

                    w[y]-=delta;

                }

            }

        }

    }

    cout << F[S] << endl;

}

 

int main(){

    freopen("input.txt","r",stdin);

    scanf("%d",&n);

    for (int i=1;i<=n;i++) scanf("%d",&w[i]);

    for (int x,y,i=1;i

        scanf("%d%d",&x,&y);

        G[x].push_back(y);

        G[y].push_back(x);

    }

    scanf("%d",&S);

    bfs();

    solve();

}

 

【D. Domino Carpet】

【题目大意】

经过一些人肉转换以后,题目变成了m*n个格子,这些格子有3种类型。

1、只允许被2*1的方块覆盖的。

2、只允许被1*2的方块覆盖的。

3、允许被1*2或2*1的方块覆盖的。

然后用2*1或者1*2的格子去完美覆盖这些格子。问有多少种方案。

n,m<=250

还有一个重要条件,假如放了一块1*2的方块占在(i,j)和(i,j+1),那么就不能存在其它的1*2方块放在(k,j-1)(k,j)或(k,j+1)(k,j+2)。

【算法分析】

由于那个重要条件,我们可以一列一列地dp转移。

要么直接整列竖的2*1方块。要么就是存在1*2方块的两列。

第一种很好算直接F[j+1]+=F[j]。

第二种可以在里面套一个dp。G[i][0]表示到达第i行,还没有放过1*2的方块,这时候的方案数。G[i][1]就是放过2*1的方块的方案数了。

然后F[j+2]+=G[m][1]*F[j]。

 

【E. Martian Food】

= =,算了10页草稿纸发现算不出来。唉,太弱小了。不会捉。

 

 

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

比赛时前3题都是迅速反应出算法,但是第3题写的时候,头脑有点混,悲剧写挂了。今天重打一遍不到10分钟就1Y了

然后就前两题那个沙茶分数排到了DIV I 150+。居然还涨rating了

SRM 503

唉,跌rating了。

250是个很骗人的题目。。。我一开始很迅速地反应到结果只能是2,1,-1。。。然后构造的方法也YY了一下。写完过掉样例以后,突然觉得好像不太靠谱,有点忐忑。然后又跑去换了个dp。最后折腾到只有130+分了。其实很显然本身就是对的啊。。。

500这种求期望的题见到就挂啊。

总是喜欢求和然后除以总方案数这种YY方式。太弱了。我YY的时候甚至觉得每个排列不是等可能的(由于第二个条件)。

然而排列和后面的选取是分步进行的。所以每个排列是等可能的。

于是可以通过枚举边,然后算对应概率,最后加起来的方式求期望值。

Codeforces Beta Round #64 【Solution】

纪念第一套自己YY切完的CF、(其实同时也是第一套切完的CF)

T_T,题意略的都是没什么技术含量纯粹为完整性的题目

 

 

A题

题意略

比赛被hack了一把。F[0]=1啊T_T。

其实就是个dp,F[i]=F[j]*2^(i-1-j) 1<=j

然后输出F[i]即可

 

 

B题

题意略

把每个sentence看成一个块,然后可以说是dp,也可以说是贪心。

F[i]表示用i段最长能覆盖到哪里,显然F[i]=F[i-1]+尽量接……

于是看什么时候能覆盖到尾巴就行了。

 

 

C题

【题意】

给定3个数Max_x Max_y w,让你求一个数对(x,y)满足这样 1<=Max_x,Max_y<=10^5 w<=10^7

for (i=1;i<=x;i++)

  for (j=1;j<=y;j++)

     if (i*j==rev(i)*rev(j)) cnt++;

最后cnt>=w,并且满足x*y最小。

输出x y或者无解输出-1

其中rev函数表示翻转。比如说rev(123217)=712321

【算法分析】

首先把式子换一下变成

 i/rev(i)=rev(j)/j

从小到大枚举i,令g=gcd(i,rev(i)),然后对i/rev(i)约分一下,再围观另外一边,显然

((i/g)*k)/((rev[i]/g)*k) = rev(j) / j……其实也就算是说j是rev(i)/g的倍数……然后就发现其实答案最多是Max_x lg Max_y级别的……然后其实可以暴力。

我们枚举x,考虑找出每个x对应的最小的y,满足1<=i<=x && 1<=j<=y内包含满足条件的数对的数目>=w。

然后x从小到大枚举,那么y必然递减,这里可以不必二分。

最终时间复杂度O(n lg n) (n=max(Max_x,Max_y))

于是有了这种代码

    int i,j,Tmp=Max_y,r;

    for (i=1;i<=Max_x;i++){

        r=rev[i]/g[i];                                                          // g[i]=gcd(i,rev[i])

        for (j=r;j<=Max_y;j+=r)

          if ((lld)(i)*j==(lld)(rev[i])*rev[j]) add(j);                 //add(i)表示将Tr[i]+1

        while (Tmp>1 && Get(Tmp-1)>=w) Tmp–;           //Get(i)表示Tr[1]+Tr[2]+Tr[3]+…+Tr[i] (于是使用树状数组维护之)

        if (Get(Tmp)>=w && (lld)(i)*Tmp

            ans=(lld)(i)*Tmp;

            ret_x=i;

            ret_y=Tmp;

        }

    }

    if (ans==INF) cout << "-1n";

    else cout << ret_x << " " << ret_y << endl;

 

 

D题

【题目大意】

一开始点集S为空,然后有Q<=10^5个询问,询问有两种类型,第一种是在点集S中插入一个点,第二种是在问,一个点是否在由点集S所构成的凸包中。

【算法分析】

其实问题就在于动态维护凸包,对于第二种询问,我们只要尝试用这个点去更新凸包,如果需要更新,那么该点在凸包外,否则在凸包内。

我们考虑把凸包上的边逆时针定向:


于是他们都变成了向量,我们以atan2(x,y)给这些向量排序(其实就是极角排序),然后再定第一条的向量的起始点为原点。然后以该原点对每个点连一条向量。

这样向量划分出了很多个区域,注意因为是凸包,所以这些向量所夹的角都为(0,pi)之间。然后我们就可以通过类似二分的方法找出该删的一个向量在哪里位置,然后暴力枚举前后的向量判断能否删除(该删的向量必然是连续的)

特别地,如果新的那个点在那个>pi的区间内,那么我们只需要检测与原点相连的边即可得到该删的向量。

对于凸包中的一个向量(x1,y1)->(x2,y2),我们判断该不该删,就是判断(x2-x1,y2-y1)^(New.x-x1,New.y-y1)是否<0, 其中^表示叉乘……

特别地叉乘等于0时要特判这个点是否在线段(x1,y1)-(x2,y2)上。

考虑动态维护一个有序序列,我们可以使用平衡树来解决。这样每个点只能被插入以及删除一次,于是是平摊lg n的。(这里不一定需要Splay)

当然几何题免不了各种恶心情况……大家可以慢慢发掘……

该题算法不难,但是实现有点恶心……属于体力题……WA了N久……

 

E题

【题目大意】

给定一棵n<=180个结点的树(树上的边长度都为1),然后让你指定一些点作为关键点(指定一个关键点需要付出k的代价),设点i到离自己最近的关键点的距离为dis(i),那么这个点需要花费d[dis(i)]的代价。(d[]在数据中给出)

【算法分析】

这个题我觉得很不错,实现不麻烦,也挺考思维的。一开始看成是一般图了,后来发现是树囧、YY良久,终于切掉。

我的方法是……dp

F[i,j]表示处理到i这个点,并且离i最近的一个关键是j,这时候最小花费的费用是多少。然后从树的叶子往根推。

我们考虑处理第i个结点,那么离它最近的关键点j有3种情况

1、i要通过自己的父亲到达j

2、i通过其中一个儿子到达j

3、i=j(这种情况处理起来和第一种其实是一样的)

先设near[i][j]表示从i跑到j,走第一步跑到哪里。比如说从1->2->3->4,那么near[1][4]=2,near[4][1]=3。

再设mat[i][j]表示从i跑到j的路径长度。

注意上面提到的3种情况中有一些状态是不能通过儿子转移上来的。

Case 1:

设当前状态为F[p][j],儿子状态是F[i][k],那么如果near[i][k]=p,但是j!=k,那么显然没有j=k时优的……

理由如下:

i->p->……->k————————这是i到离自己最近关键点的路径

    p->……->j————————这是p到离自己最近关键点的路径

……既然p->……->j是最短的,那么显然i->p->……->j也是最短的T_T。

 

Case 2:

设当前状态为F[p][j],儿子状态时F[i][k],那么如果near[p][j]=i,但是j!=k,那么显然没有j=k时优的……

理由类似上面:

p->i->……->j

     i->……->k

然后你们懂的T_T

 

于是就可以转移了。你们可能觉得这不太正确……那么继续来YY。

考虑F[i,j]所包含的信息,在i为根的子树里,如果不需要通过i跑上来的点,显然在下面已经指派完成了的不用管。

如果要通过i跑上来的点,那么它们所需要的点都是j,于是,F[i,j]所包含的信息对我们推导后面的状态是一样的,于是同样是达到(i,j)这种状态,我们只需要取最优的一个就可以了,所以这个状态定义是正确的……

由于一棵树有n条边,每次转移需要n^2,所以一共要n^3

下面附上代码。

 #include 

#include #include #include #include using namespace std;
typedef long long lld;
const lld INF=(lld)(1)<<50;
int n,price;
int w[205];
lld mat[205][205];
int near[205][205];
lld F[205][205];
int belong[205];

void Floyed(){
  int i,j,k;
  for (k=1;k<=n;k++)
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
      if (mat[i][k]+mat[k][j]        mat[i][j]=mat[i][k]+mat[k][j];
}

void Make_Near(){
  int i,j,k;
  for (i=1;i<=n;i++)
    for (j=1;j<=n;j++)
    if (i!=j){
      for (k=1;k<=n;k++)
        if (mat[k][j]==mat[i][j]-1 && mat[i][k]==1) near[i][j]=k;
    }
}

void dfs(int p,int fa){//dp的转移
  int i,j,k;
  for (i=1;i<=n;i++)
    if (mat[p][i]==1 && i!=fa)
      dfs(i,p);
  for (j=1;j<=n;j++)
    if (p==j)
    F[p][j]=price;                    //price是题目中提到的k,表示建一个关键点需要的代价
    else
    F[p][j]=w[mat[p][j]];             //w是题目中的d[]数组
  for (i=1;i<=n;i++)                    //F[p][j] Get message from  F[i][k]
    if (mat[p][i]==1 && i!=fa)          //i是p的儿子
      for (j=1;j<=n;j++){
      lld Min=INF;
      for (k=1;k<=n;k++){
      if (near[i][k]==p && k!=j) continue;      //文中提到的Case 1
      if (near[p][j]==i && k!=j) continue;      //文中提到的Case 2
      if (F[i][k]        Min=F[i][k];
      }
      F[p][j]+=Min;
    }
}

void Out(int p,int j,int fa){
  belong[p]=j;
  for (int i=1;i<=n;i++)
    if (mat[p][i]==1 && i!=fa){
    lld Min=INF;
    int Mini=0;
    for (int k=1;k<=n;k++){
      if (near[i][k]==p && k!=j) continue;
      if (near[p][j]==i && k!=j) continue;
      if (F[i][k]        Min=F[i][k];
        Mini=k;
      }
    }
    Out(i,Mini,p);
    }
}

void output(){
  lld Min=INF,Mini=0;
  for (int i=1;i<=n;i++)
    if (F[1][i]      Min=F[1][i];
      Mini=i;
    }
  cout << Min << endl;
  memset(belong,0,sizeof(belong));
  Out(1,Mini,0);
  for (int i=1;i<=n;i++) cout << belong[i] << " ";
  cout << endl;
}

int main(){
  scanf("%d%d",&n,&price);
  for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
      if (i==j)
      mat[i][j]=0;
      else
      mat[i][j]=INF;
  w[0]=0;
  for (int i=1;i  for (int x,y,i=1;i    scanf("%d%d",&x,&y);
    mat[x][y]=mat[y][x]=1;
  }
  Floyed();
  Make_Near();
  dfs(1,0);
  output();
}