20131024: 树状数组入门; 树堆入门; 二叉树的树形打印

本来是想今天把树状数组的第二道(深化)也过了然后一起写总结的, 哪知道死活过不了。。。一片红啊~~


树状数组主要是将一个数组分成一段段并求和, 要真正理解透彻还是需要好好看看资料的, 其中与位运算相关的lowbit(x)特别有用, 另外就是更新与求和两个操作也是树状数组必须的, 复杂度都是O(lgn), 应该说时间上还是一个很大的优化!~

另外说下在poj上做题的小经验, 不管用的算法再好, 当输入输出十分巨大时, 不用scanf, printf是要跪的, 因为cin, cout确实很慢, 而且不是很方便设置输入的格式, 相比之下

scanf的输入格式十分灵活(万能的有木有!!!~~~), 但是如果不能清晰掌握的话也容易出错!~


第一道是树状数组入门题, 主要就是考察这一基本数据结构如何使用。

/*
  Name: Stars
  Copyright: from poj
  Author: xiaoli
  Date: 23/10/13 00:22
  Description: 树状数组入门, 建立,查询, 更新 
*/
//poj 2481 cows    树状数组深入






#include<cstdio>         //lowbit可优化, 防止反复调用
#include<iostream> //注意scanf, printf都具有时间优越性!
#define MAX 32005        //n是指数据个数, 这里需要确定的是数据的范围以确定数组大小!!!      
using namespace std;


int level[15005]={0};      //记录层次
int c[MAX]={0};          //树状数组,记录每一段树的符合要求的点数
//注意树状数组要求的下标均为正整数!读入和操作时需要简单转化
int lowbit(int x)        //算出最右边一个1对应的值, 即是树状数组中x对应的子树长度
{
return x&(-x);       //位运算技巧
}


void update(int x)       //每插入一个x,应该更新其所有父节点的值(即是父节点统计的树中的一个!)
{
while(x<MAX)
{
c[x]++;
x+=lowbit(x);
}
}


int sum(int x)           //求出x对应的层次, 因为y一定是升序, 不需考虑(自身还未加入)
{                        //注意每读入一个x在其插入前计算层次,因为不能包含其自身
int ans=0;
while(x>0)
{
ans+=c[x];
x-=lowbit(x);    //前面一棵树的根的下标, 到0表示结束
}
return ans;
}


int main()
{
int n,i,a,b;
scanf("%d", &n);              //n不要改变
for(i=0;i<n;++i)
{
scanf("%d %d",&a, &b);
//不存储节点, 而是每读入一个计算出这个节点对应的层次,节省空间, 方便操作
//这样做可行是因为一个节点对应的在它左下角的必然在之前被读入
//然后我们再统计这个节点的x之前的即可,即将对应的树状数组相加
a++;                      //转化为正整数,统一标准
level[sum(a)]++;          //每计算出一个节点层次将对应层次加1
update(a);                //新插入一个节点
}


for(i=0;i<n;++i)              //层次的范围是0-n-1
printf("%d\n", level[i]);
return 0;
}



关于第二道, 还是自己太弱了, 我已经弄懂了它的原理和算法, 应该说, 主要就是求一个序列的逆序数, 但是由于个数n非常大, 所以需要在采用树状数组的前提下每插入一个查询一次, 这样时间复杂度降至O(n), 另外, 由于单个元素的分布过于稀疏, 单纯用数组存储很浪费空间且不一定够, 所以要采取离散化, 离散化可以用排序+二分查找,

也可以用结构体+数组循环,       基本上做了这些之后就没问题了, 这是在输入数据没有相同的情况下!!~

但友好的poj果断提供了相同数据, 于是不能用不稳定排序(sort, qsort), 改用稳定排序(冒泡, stable_sort等), 好像离散采取二分的方法也会大乱顺序, 但是我都修改后还是WA, 很郁闷!~ 网上有人说可能是得用long long, 但是应该只有最后的结果(总的逆序数)可能超出整数范围才对, 这应该不是问题!~

好吧,暂时还是先贴出来吧, 各位要是有想法一定要在评论里指点一下啊!!!!!~~~~~


//树状数组的深化练习,配合离散(两种方式), 稳定排序(对于数字相同的情况)
//还是失败了!~~~








#include<iostream>    //要求冒泡排序中的交换次数, 其实就是求逆序数!                     
#include<cstdio>      //应该要保证各个数互不相同!!!!(无论是找小还是直接找大的)
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define MAX 500005
#define initial(p) memset(p,0,sizeof(p))     //不应注明int*
using namespace std;


inline int lowbit(int x)                    //用inline可提高速度
{
return x&(-x);
}
/*
int cmp(const void*p1, const void*p2)
{
return *(int*)p1 - *(int*)p2;
}
*/
int n=0;
long long ans=0;
int a[MAX],b[MAX],c[MAX]; //c中存储的是当前这段树的小于父节点且在其之前出现的个数
struct point
{
int num;
int id;
}dot[MAX];
bool operator<(const point&a, const point&b)     //比较法则
{
return a.num<b.num;
}
/*
void discrete()                       //进行离散化, 化为1~n的编号记录(数据间隙大)
{                                     //采用排序与二分查找
int l=1,r=n,mid=0,t=0;
for(int i=1;i<=n;++i)
{
t=a[i];l=1;r=n;
while(r>=l)
{
mid=(l+r)/2;
if(t<b[mid])
r=mid-1;
else if(t>b[mid])
l=mid+1;
else
{
a[i]=mid;
break;
}
//这里保证一定能找到
}
}
}
*/


int  sum(int k)                      //求出在k之前出现的且更小的(不用交换的)
{
int result=0;
while(k>0)
{
result+=c[k];
k-=lowbit(k);
}
return result;
}


void update(int k)                   //每插入一个a[i]需要更新对应的c[]
{                                    //在计算完后更新(计算时不能计入当前节点)
while(k<=n)                     //已知n是其界
{
c[k]++;
k+=lowbit(k);                //移动到父节点
}
}


void build()                          //建立树状数组
{
initial(c);
int i=0,j=0;
for(i=1;i<=n;++i)
{
j=a[i];
ans+=(i-1-sum(j));            //加上之前出现的更大的(需要交换), 即这个数对应的逆序数
update(j);
}
}


int main()
{
int i=0,j=0;
while(scanf("%d",&n)&&n)
{
for(i=1;i<=n;++i)
{
scanf("%d",&dot[i].num);
dot[i].id=i;
}
//qsort(b+1, n, sizeof(int), cmp);
stable_sort(dot+1,dot+n+1);           //稳定排序
//discrete();
for(i=1;i<=n;++i)                 //稳定的离散化???
a[dot[i].id]=i;
ans=0;                           //每一轮都要初始化
build();
//注意ans可能很大,应该定义为长整形并按64位输出
printf("%I64d\n", ans);
}
return 0;
}






/*合并排序求逆序数
#include <iostream>
#include <algorithm>
using namespace std;


#define MAXN 500001


int a[MAXN],t[MAXN],n;
__int64 ans;


void Merge(int l,int m,int r){
    int i=0,j=l,k=m+1;
    while(j<=m && k<=r){
        if(a[j]>a[k]){
            t[i++]=a[k++];
            ans+=m-j+1;
        }
        else
            t[i++]=a[j++];
    }
    while(j<=m)
        t[i++]=a[j++];
    while(k<=r)
        t[i++]=a[k++];
    for(j=0;j<i;j++){
        a[l+j]=t[j];
    }
}




void Mergesort(int l,int r){
    if(l<r){
        int m=(l+r)/2;
        Mergesort(l,m);
        Mergesort(m+1,r);
        Merge(l,m,r);
    }
}




int main(){
    int i;
    while(scanf("%d",&n) && n){
        ans=0;
        for(i=0;i<n;i++)
            scanf("%d",a+i);
        Mergesort(0,n-1);
        printf("%I64d\n",ans);
    }
    return 0;
}
*/


后面附加的是归并排序解法(提交也是WA), 最近经常看到这个算法, 貌似很好很强大, 找个机会一定要好好学习下!~



今天干掉了一道树堆的入门题(treap) ,耗了不少时间, 第二道题时间没时间做~只能找个机会再写, 估计会拖几天, 还是先把入门的总结了吧!~

树堆, 与笛卡尔树结构相同, 就是树+堆, 树是二叉搜索树, 堆就是数据结构中的最大(小)堆, 对于每个节点, 至少有两个标识分别对应两者, 而我们在插入等操作时也需要维护两种数据结构,当然在实际操作时我们往往通过按某个关键字排序使问题简化为维护一种数据结构!~

树堆是一种很方便地实现二叉搜索树的结构, 而且二叉搜索树在极端情况下会深而窄, 这时二叉树在时间上的优越性就几乎没有了, 所以我们需要构造的是平衡二叉搜索树, 

即通过随机化生成第二关键字的方法来构建树堆, 这样也就实现了平衡二叉搜索树的要求和功能。在这个应用中, 二叉搜索树是主体, 堆只是用来维护其宽度的辅助工具,

这里回顾下生成随机数的方法, 直接调用生成函数生成的是伪随机数, 只有先以时间为种子再调用生成函数才能生成真正的随机数, 随机数有最大范围(必须的), 我们常常只想得到一个小范围内的随机化数, 这时就要借助模运算了!~

还有很多细节和注意点, 以及树堆的基本使用方法都在程序中说明了~:


/*
  Name: Binary Search Heap Construction
  Copyright: from poj
  Author: xiaoli
  Date: 23/10/13 20:22
  Description: treap   树堆(笛卡尔树,结构相同)
  1.第一关键字满足搜索序,第二关键字满足堆序.首先将第一关键字(搜索序的关键字)排序(笛卡尔树中序遍历结果是按照第一关键字升序的),
  然后逐个向树中插入元素,这时只需要考虑后插入的元素的优先级即可。
  2.
  a、优先级比上一个节点优先级高:将这个节点调整为根节点。
  b、优先级比上一个节点优先级低:将这个节点设为当前节点的右儿子。
  3.但是还有: 分治+RMQ, 其中,RMQ问题有两种方法:线段树和Sparse Table算法。
  依然是现按第一关键字排序,然后就是区间询问最大值,这个最大值就是整个树的根,然后分别对左右两个区间进行查询即可。
  4.笛卡尔树定义:
  笛卡尔树是一棵二叉树,树的每个节点有两个值,一个为key,一个为value。
  光看key的话,笛卡尔树是一棵二叉搜索树,每个节点的左子树的key都比它小,右子树都比它大;
  光看value的话,笛卡尔树有点类似堆,根节点的value是最小(或者最大)的,每个节点的value都比它的子树要大。
*/


//数组的每一个都可以看做一个树,堆,树堆, 注意父节点与子节点的编号对应(数组存储时容易乱!)
#include<iostream>                          
#include<cstdio>                          
#include<cstring>
#include<cstdlib>
#define MAX 50005
using namespace std;


int n=0;
struct point
{
int priority,lson,rson,parent;
char s[100];
}dot[MAX];


int cmp(const void*p1,const  void*p2)
{
point *t1=(point*)p1, *t2=(point*)p2;
return strcmp(t1->s, t2->s);
}


//第0个节点是虚拟的,方便操作, 只引出右儿子
void Insert(int p)
{
int pre=p-1;               //前一个节点
while(dot[pre].priority<dot[p].priority)
pre=dot[pre].parent;
//将待插入节点插入到右端(保证二叉搜索树性质)
dot[p].lson=dot[pre].rson;  //保存父节点的右子树, 放在左端,同时维护二叉搜索树
dot[pre].rson=p;
dot[p].parent=pre; //修改其父节点       
}


void Travel(int p)              //按左中右的顺序遍历 
{
if(p==0) return;            //注意子节点为空时标记是0
printf("(");
Travel(dot[p].lson);
printf("%s/%d",dot[p].s,dot[p].priority);    //简化的输出方法 
Travel(dot[p].rson);
printf(")");
}


int main()
{
int i,j,sum=0;
while(scanf("%d",&n)&&n)
{
for(i=1;i<=n;++i)
{   //注意处理输入的技巧
            //注意dot[i].s本身就是字符串的首地址 
scanf(" %[a-z]/%d",dot[i].s,&dot[i].priority); //注意前面有个空格不能读入字符串
dot[i].lson=dot[i].rson=dot[i].parent=0;
}
qsort(dot+1,n,sizeof(point),cmp);
dot[0].lson=dot[0].rson=dot[0].parent=0;
//dot[0].s和dot[0].priority的初始化
dot[0].priority=0x7fffffff;          //定为最大,虚拟根不允许改变
for(i=1;i<=n;++i)
Insert(i);                       //插入标号为i的节点
Travel(dot[0].rson);
printf("\n");
}
return 0;
}








//线段树RMQ版本:
/*
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;


struct node {
    char s[100];
    int val;
} a[50005];
int n, v[50005<<2];


void build(int l, int r, int rt) {
    if (l == r) {
        v[rt] = l;
        return ;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt<<1);
    build(mid+1, r, rt<<1|1);
    v[rt] = (a[v[rt<<1]].val > a[v[rt<<1|1]].val) ? v[rt<<1] : v[rt<<1|1];
}
int query(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R)
        return v[rt];
    int ret1, ret2, mid = (l + r) >> 1;


    ret1 = ret2 = 0;
    if (L <= mid) ret1 = query(L, R, l, mid, rt<<1);
    if (mid < R) ret2 = query(L, R, mid+1, r, rt<<1|1);
    if (ret1 == 0) return ret2;
    if (ret2 == 0) return ret1;
    return (a[ret1].val > a[ret2].val) ? ret1 : ret2;
}
void print(int l, int r) {
    if (l > r) return ;
    if (l == r) {
        printf("(%s/%d)", a[l].s, a[l].val);
        return ;
    }
    int m = query(l, r, 1, n, 1);
    printf("(");
    print(l, m-1);
    printf("%s/%d", a[m].s, a[m].val);
    print(m+1, r);
    printf(")");
}
bool cmp(node x, node y) {
    return strcmp(x.s, y.s) < 0;
}
int main() {
    while (scanf("%d", &n) == 1 && n) {
        for (int i=1; i<=n; i++)
            scanf(" %[a-z]/%d", a[i].s, &a[i].val);


        sort(a+1, a+n+1, cmp);
        build(1, n, 1);
        print(1, n);
        printf("\n");
    }
    return 0;
}
*/



后面的RMQ版本是从网上搜到的, 感觉很强大, 但是我还不会用, 作为收藏附上!~




今天最后一个问题也是最揪心的一个问题是关于我们的数算A练习中“表达式-表达式树-表达式求值”这道题, 前面的学习日记中也提到过, 很让人郁闷的高综合的题啊!!!~

大意:输入一个算术表达式, 数字用字母代表, 不包含空格小数等等, 再输入字母与数字的匹配关系(建议用map存储节省查找时间, 实际上是HASH的运用),

要求输出后缀表达式并求值, 建立二叉树并树形打印(层遍历+控制输出)

算术表达式去括号即是中缀表达式, 利用栈也可以方便地转化为后缀表达式并求值, 中缀表达式与后缀表达式可以说分别与中缀序列和后缀序列相对应, 但是:

因为它们是算术表达式产生的序列, 所以必然具有其特殊性!!!

之前做过一道借由中缀和后缀序列重构二叉树的题目, 所以我在这道题中一开始就是这样去建树,所以后面死活过不了!!!!

之前的那道题目它的序列中是没有相同元素的, 事实上绝大多数二叉树都是元素不同的, 否则没啥意义, 所以那道题的重构没有问题(无论是栈还是递归), 也可以用map来优化查找,  但是, 表达式中相同元素的情况比比皆是, a+b+c, a+b+a, 一个字符对应多个位置, 所以不能用map来优化查找, 那我们改用直接在当前子树中枚举查找好了,

还是不行, 无论是从大到小还是从小到大都会有问题, 因为当前子树中也完全可以出现多个相同的, 无论怎么选择都是不合理的!!·比如下面两个测试数据:

(a+b*c/(d+e+f))
6
a 1
b 1
c 1
d 1
e 1
f 1








a*(b+c/d)+c*h-g*f
7
a 1
b 1
c 1
d 1
f 1
g 1
h 1

值得一提的是, 这可不是poj给的(它就给最简单的数据), 这是我和室友自己构造出来的, 从这也得到一个启示, 不要觉得算法大体对了就行, 可能完全走错了方向, 二检验必须依靠数据, 所以自己构造一些奇葩数据是至关重要的一点, 这也是poj这种训练模式希望培养的吧!~

利用两个序列重构二叉树不可行了, 但是关注后缀算术表达式(逆波兰式)的特殊性质, 每个运算符后面必然跟着两个操作对象, 是有且仅有!!!

根据这个性质直接从后向前枚举或递归即可正确建立二叉树!!!~

终于把表达式树正确地建出来了~

但是一个新问题又来了, 树形打印可不好做哦, 树形打印, 直观的要求是当填补成为满二叉树时(是指真正意义上的全满, 因为满二叉树在不同地方定义不同), 最底层

(叶子节点)标号是0,2,4,。。。。。。                                         然后父节点等于左右之和/2,    所以我们必须借助最底层求出每个节点的横坐标, 然后在层遍历时记住光标的位置和深度(因为要求末尾都不能打出空格, 所以先输出空格后输出节点!!!), 另外还需要一个数组来记录树形分支符的位置!~总的来说, 还是特别麻烦的!~

实事求是, 我的程序还不完美, 因为在一些特别输入下空格会打多了, 当然大体形态没变, 还需要最后一步调整, 但现在也没啥时间了, 而且在Poj上也通过了, 这是很不容易的,这说明poj对树形打印的要求放的很松, 或者是测试数据不够!~

程序摘录如下:


//对二叉树进行层遍历并按要求输出,最后利用栈计算后缀表达式的值
//运算符表达式中元素可相同, 用map会出现问题!
//大体已经没有问题,但对于比较复杂的表达式打印会出现问题(poj上AC是它的测试数据不够)
#include<iostream>
#include<stack>
#include<cstring>
#include<map>
#include<queue>
using namespace std;


char s[10000]={0},s1[10000]={0},s2[10000]={0};
int a[10000]={0};                  //用来管理对分支符的输出,1 / 0 ' ' -1 \


stack<char> v1;
stack<int> v2;


map<char,int> match;
map<char,int> priority;
map<char,int> v;


struct point
{
char c;
point*left;
point*right;
int x;                            //输出时应该对应的横坐标
int deepth;                       //对应的深度
};


queue<point*> v3;
/*
void pre_order(point*p)
{
cout<<p->c;
if(p->left)
pre_order(p->left);
if(p->right)
pre_order(p->right);
}
void mid_order(point*p)
{
if(p->left)
mid_order(p->left);
cout<<p->c;
if(p->right)
mid_order(p->right);
}
void after_order(point*p)
{
if(p->left)
after_order(p->left);
if(p->right)
after_order(p->right);
cout<<p->c;
}
*/


int pos=0,h=0;
point* create(int depth)
{
if(depth>h) h=depth;                //找最大深度
point*p1,*p2;
int i,j;
char c=s2[pos];
pos--;                               //移动到下一位
p1=new point;
p1->c=c;
p1->deepth=depth;
if(c=='+'||c=='-'||c=='*'||c=='/')    //为运算符
{
p1->right=create(depth+1);        //先建立右子树
p1->left=create(depth+1);
}
else                                 //为数字(字母)
{
p1->right=NULL;
p1->left=NULL;
}
return p1;
}




int x_pos=0;
void get_x(point*p)
{
int t,t1,t2;
if(p->left==NULL&&p->right==NULL)
{
t=1<<(h-p->deepth);                     //越过的个数
t1=x_pos;
x_pos+=(2*t);
t2=x_pos-2;
p->x=(t1+t2)/2;
}
else if(p->left==NULL&&p->right!=NULL)
{
t1=x_pos;
t=1<<(h-p->deepth-1);
x_pos+=(2*t);
t1=(t1+x_pos-2)/2;
get_x(p->right);
t2=p->right->x;
p->x=(t1+t2)/2;
}
else if(p->left!=NULL&&p->right==NULL)
{
get_x(p->left);
t1=p->left->x;
t2=x_pos;
t=1<<(h-p->deepth-1);
x_pos+=(2*t);
t2=(t2+x_pos-2)/2;
p->x=(t1+t2)/2;
}
else if(p->left!=NULL&&p->right!=NULL)
{
get_x(p->left);
get_x(p->right);
p->x=(p->left->x+p->right->x)/2;
}
}


int main()
{
int i,j,k;
cin>>s;
char c;
int n;
cin>>n;
while(n--)
{
//c=cin.get();                            这里输入没有问题,对cin的机制不清晰
cin>>c>>k;                               //跳过回车符但不跳过空格?
match.insert(make_pair(c,k));              //建立匹配关系,方便查找
}
priority.insert(make_pair('+',1));
priority.insert(make_pair('-',1));
priority.insert(make_pair('*',2));
priority.insert(make_pair('/',2));
priority.insert(make_pair('(',0)); //将其规定为最小可防止转化为后缀时将括号也取出!!!
priority.insert(make_pair(')',0)); //若不规定,则在取出时应该判断是否为四则运算符再看优先级





//建立后缀序列(也即逆波兰表达式), 需要用栈临时存放运算符
i=0;j=0;
int len=strlen(s);
for(i=0;i<len;++i)
{
if(s[i]>='a'&&s[i]<='z')
s2[j++]=s[i];
else if(s[i]=='(')
v1.push(s[i]);
else if(s[i]==')')
{
while(!v1.empty())         //先判断非空
{
c=v1.top();
if(c=='(')
break;
v1.pop();
s2[j++]=c;
}
v1.pop();
}
else                              //四则运算符
{
while(!v1.empty())           //须先判断非空
{
c=v1.top();               //不可能出现这里中断但前面还有优先级更大的,
if(priority[c]<priority[s[i]])              //因为这样前面会被排除掉!
break;
v1.pop();
s2[j++]=c;
}
v1.push(s[i]);
}
}
while(!v1.empty())
{
s2[j++]=v1.top();
v1.pop();
}
s2[j]='\0';
cout<<s2<<endl;


//利用后缀表达式计算表达式的值,期间要用到匹配关系match

for(i=0;s2[i];++i)
{
if(s2[i]<='z'&&s2[i]>='a')
{
v2.push(match[s2[i]]);
continue;
}                     //对于合法的后缀表达式,计算中不会出现出栈遇空的现象
else if(s2[i]=='+')   //所以在此不用另外判断栈是否非空
{
k=v2.top();
v2.pop();
k+=v2.top();
v2.pop();
v2.push(k);
}
else if(s2[i]=='-')
{
k=v2.top();
v2.pop();
j=v2.top();
v2.pop();
v2.push(j-k);
}
else if(s2[i]=='*')
{
k=v2.top();
v2.pop();
k*=v2.top();
v2.pop();
v2.push(k);
}
else if(s2[i]=='/')
{
k=v2.top();
v2.pop();
j=v2.top();
v2.pop();
v2.push(j/k);
}
else continue;
}



//对于表达式,直接利用后缀建立(用两种序列反而复杂话!!!)
k=strlen(s2);
/*
for(i=0;i<k;++i)
v.insert(make_pair(s1[i],i));
*/
pos=k-1;
point* head=create(0);
//pre_order(head);cout<<endl;
//mid_order(head);cout<<endl;
//after_order(head);cout<<endl;


//层遍历二叉树并按规定格式输出


get_x(head);                   //先求出各个节点对应的横坐标

point *t1,*t2;
t1=head;
v3.push(t1);
int pos=0;                     //记录当前层次
int cnt=0;                     //当前光标的横坐标
int limit=0;   //记录边界


while(!v3.empty())
{
t1=v3.front();
v3.pop();
if(t1->deepth>pos)
{
pos++;
cout<<endl;
for(i=0;i<limit;++i)
{
if(a[i]==0)
cout<<' ';
else if(a[i]==1)
cout<<'/';
else if(a[i]==-1)
cout<<'\\';
}
cout<<endl;                    //进入下一行的输出状态
memset(a,0,sizeof(a));
limit=0;
cnt=0;       
}


for(i=cnt;i<t1->x;++i)
cout<<' ';
cout<<t1->c;
cnt=i+1;


t2=t1->left;
if(t2!=NULL)
{
v3.push(t2);
a[t1->x-1]=1;                   //为当前父节点找好分支符的对应
limit=t1->x;
}
t2=t1->right;
if(t2!=NULL)
{
v3.push(t2);
a[t1->x+1]=-1;                  //要使用当前父节点
limit=t1->x+2;
}
}
cout<<endl<<v2.top()<<endl;
return 0;
}



算法over!~

今天的一个小插曲是借着兴头装了一个三系统, ubuntu-kerlin 13.10, 不愧是适合中国用的版本, 看着界面就很舒服, 呵呵, 果然, 多系统是可以随心所欲装的嘛!~

而且只要安装时把引导设在/boot分区, 后面在windows下用easybcd把它加入引导项设置保存即可(还是推荐备份下吧!~)


今天还是蛮有成就感的,爽歪歪, 迎接明天的10节课!~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值