[UOJ#84]水题走四方

版权声明:本文是蒟蒻写出来的,神犇转载也要说一声哦! https://blog.csdn.net/WerKeyTom_FTD/article/details/79911144

题目大意

一颗树,两个人初始从根节点出发,每一步每个人可以选择原地不动或者走向某个儿子。一步后,一个人可以瞬移到另一个人所在节点上。
最少需要多少步,使得每个节点都被遍历过?

做法

我们可以认为存在一个本体以及一个分身,每次都是分身瞬移到本体的位置。
如果本体A和分身B在节点C分道扬镳,本体A在D节点瞬移至分身B所在的节点E,干脆一开始便让本体A往E走,分身B往D走。因此瞬移的总是分身。
最后一定是停在一个叶子的位置。即本体走过的是一条主链。
本体肯定总是走走停停,不妨把停的位置标记为关键点,那么过程总是这样的:
本体在某个关键点停住,分身开始不断遍历该关键点至下一个关键点间所有延伸子树的叶子,剩最后一个叶子时本体也开始动身前往下一个关键点。
剩的这个叶子当然是深度最大的叶子。
可以设dp f[x]表示从根走到u,沿途延伸子树都被遍历,x是一个关键点,最少步数是多少。
不妨记ds[x]表示x子树内叶子深度和,nl[x]表示x子树内叶子个数,d[x]表示x的深度。
记dm[u,v]表示u是v的祖先,u到v(不包含v)的延伸子树叶子最大深度。
则转移就是枚举上一个关键点。
考虑优化。
假设上一个关键点为u,现在这个关键点为v。
如果分身最后走的那个叶子深度并没有达到v的深度,显然分身到达叶子之后就可以立马瞬移然后和本体共同行走到v。
由于走的是最深的叶子,瞬移回来后的路程都不可能有延伸子树。
那么我们为何不设瞬移回来的那个点为关键点呢?
这样我们就只需要考虑dm[u,v]>=d[v]的u作为关键点。而且要求dm[u,v]要出现在u的延伸子树里(即若w是u的下一个点,有dm[u,v]>dm[w,v])。
然后需要新增一种转移,如果父亲只有一个儿子,可以直接从父亲的f+1转移过来。
然后我们不难考虑到,如果v存在两个可以作为关键点的祖先u1,u2,d[u1]<d[u2],上一个关键点就是u1,而不将u2也设置为关键点,一定是不优的,因此我们只需要考虑up[v]表示最深的u满足dm[u,v]>=d[v]。
现在只要能处理up,本题dp可以线性解决。
比较容易直接想到的思路是树上可恢复形单调栈,可以配合长链剖分优化至线性。
我们可以想到一个很简单的方法,每个子树保存一个链表,按照d排序保存不能在子树内找到up的点。
接下来考虑合并一个子树,关键在于合并两个链表。
这里可以发现,两个链表一定会有一个变为空,即只有当前的长儿子链表会不为空,其余的儿子由于不是最长,其链表所有元素都会因为长儿子被T掉。
这样就能做到线性,速度快常数小易理解,代码也非常好写。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=5000000+10;
const ll inf=1000000000000000000;
char s[maxn*2];
int p[maxn],nl[maxn],dm[maxn],d[maxn],son[maxn],nxt[maxn],up[maxn];
ll f[maxn],ds[maxn];
int i,j,k,l,t,n,m,tot,now;
ll ans;
int main(){
    scanf("%d",&n);
    scanf("%s",s+1);
    now=0;
    fo(i,1,n*2)
        if (s[i]=='('){
            p[++tot]=now;
            now=tot;
        }
        else now=p[now];
    fo(i,2,n){
        d[i]=d[p[i]]+1;
        son[p[i]]++;
    }
    fd(i,n,2){
        if (!son[i]){
            ds[i]=dm[i]=d[i];
            nl[i]=1;
        }
        now=i;
        while (now&&d[now]<=dm[p[i]]){
            up[now]=p[i];
            now=nxt[now];
        }
        while (nxt[p[i]]&&d[nxt[p[i]]]<=dm[i]){
            up[nxt[p[i]]]=p[i];
            nxt[p[i]]=nxt[nxt[p[i]]];
        }
        if (now) nxt[p[i]]=now;
        nl[p[i]]+=nl[i];
        ds[p[i]]+=ds[i];
        dm[p[i]]=max(dm[p[i]],dm[i]);
    }
    fo(i,2,n){
        f[i]=inf;
        if (up[i]) f[i]=f[up[i]]+ds[up[i]]-ds[i]-(ll)(nl[up[i]]-nl[i])*d[up[i]];
        if (son[p[i]]==1) f[i]=min(f[i],f[p[i]]+1);
    }
    ans=inf;
    fo(i,1,n)
        if (!son[i]) ans=min(ans,f[i]);
    printf("%lld\n",ans);
}

新手的水题

08-05

[url=http://www.zerojudge.tw/ShowProblem?problemid=a450][/url]rnrn以上为题 以下为代码 想了很久没对 实在是想不下去了 给个突破口rnrn[code=c]rn#includern#includernrnusing namespace std;rnrnintrn c, /*计数器*/rn q, /*Q筆詢問*/rn n, /*N根棒棒糖*/rn i, //循环变量 rn j, //循环变量rn k, //循环变量 rn candy[1000000], /*N根棒棒糖*/rn mid, //n跟棒棒糖的中间数rn shuru[1000000], //q笔询问 rn publicc;rnrnvoid print2();rnvoid print1();rnvoid qchange1();rnvoid qchange2();rnrnvoid qchange1( long l , long r )rn rn mid = ( l + r ) / 2 ;rn rn if( shuru[1] < candy[mid] )rn rn if( l+1 != mid ) qchange1( l , mid ) ;rn rn else shuru[1] = mid ;rn rn rn if( shuru[1] == candy[mid] ) shuru[1] = mid ;rn rn if( shuru[1] > candy[mid] )rn rn if( mid+1 != r ) qchange1( mid , r ) ;rn rn else shuru[1] = r ;rn rnrnrnvoid qchange2( long l , long r )rn rn mid = ( l + r ) / 2 ;rn rn if( shuru[2] < candy[mid] )rn rn if( l+1 != mid ) qchange2( l , mid ) ;rn rn else shuru[2] = mid ;rn rn rn if( shuru[2] == candy[mid] ) shuru[2] = mid ;rn rn if( shuru[2] > candy[mid] )rn rn if( mid+1 != r ) qchange2( mid , r ) ;rn rn else shuru[2] = r ; rn rnrnrnvoid print1()rn rn printf( "%d\n" , shuru[2] - shuru[1] ) ;rnrnrnvoid print2()rn rn printf( "The candies are too short \n" ) ;rnrnrnint main()rn rn scanf( "%d %d" , &n , &q ) ;rn rn for( j = 1 ; j <= n ; j ++ )rn rn scanf("%d",&candy[j]);rn rn for( k = 2 ; k <= j ; k ++ )rn rn candy[0] = candy[k] ; publicc = k-1 ;rn rn while( candy[0] < candy[publicc] )rn rn candy[publicc+1] = candy[publicc] ;rn rn publicc -- ;rn rn rn candy[publicc+1] = candy[0] ;rn rn rn rn for( k = 1 ; k <= q ; k ++ )rn rn for( j = 1 ; j <= 2 ; j ++ ) scanf( "%d" , & shuru[j] ) ;rn rn /* 1. 定义一个数组存储n组数据 OK*/rn /* 2. 将n组数据进行排序 OK*/rn /* 3. 将前后两个分界点利用数组的下标代替,相减的差为答案*/rn /* 4. shuru[1] <= x <= shuru[2] /rn rn /* 3,4 实在太难了T T*/rn rn qchange1( 1 , n ) ;rn rn qchange2( 1 , n ) ;rn rn if( shuru[1] < shuru[2] ) print1();rn rn else print2();rn rn // printf( "%d %d\n" , shuru[1] , shuru[2] );rn rnrn return 0;rn[/code]

一道水题

07-26

阶乘之和rn时间限制(普通/Java):5000MS/10000MS 运行内存限制:65536KBytern总提交:143 测试通过:41rnrn描述rnrnrn约翰(1903,12,28-1957,2,8)出生于纽曼,是一个在数学、逻辑、量子物理学、气象学、科学,计算机和游戏理论的基础研究方面有重要的贡献的匈牙利-美国数学家。 他以惊人的记忆和理解及解题速度而闻名。在 1925 年,他获得了苏黎士学院的化学工程学位,在 1926 年获得布达佩斯大学的数学博士学位,他的博士论文在集合论上做出了一个重要的贡献。 在 他20岁的时候, 他提出的一种顺序数字定义被广泛采用。 当在他的二十几岁的时候, 他在纯数和应用数学方面作了许多贡献,为他成为数学家建立了深厚的基础。rnrn他的量子力学 (1932) 的数学基础为新的科学训练建立了坚硬的架构。 在这次期间,他也证明游戏理论的最小-最大定理。 他逐渐地在游戏理论中扩大了他的工作,而且与合作者 Oskar Morgenstern写了游戏和经济的行为理论(1944)。rnrn有一些数,可以表示成一些阶乘的和。例如9,9=1!+2!+3!。约翰对这样的数非常有兴趣。因此,他给你一个数n,并且要求你告诉他是否该数可以表示成几个数的阶乘的和。rnrn这是非常简单的。对于给定的n,你只需检查是否有一些Xi,当n=x1!+ x2!+…+ xi!(i≥1,t≥1,x≥0,xi=xj当且仅当i=j),如果存在,就说“YES”,否则打印“NO”。 rnrn输入rnrn输入文件由非负整数n(n<=1,000,000),每一行一个数。输入以负数结束。rnrn输出rnrn对每一个n,打印“YES”或“NO”。每行一个。没有多余的空格。rnrn样例输入rnrnrn9rn-1rnrn样例输出rnrnrnYESrnrn下面是我的代码:rn#include rnusing namespace std;rnint a[10]=1,1,2,6,24,120,720,5040,40320,362880;rnint main()rnrnint n,i;rnwhile(cin>>n,n>=0)rnrnbool flag=false;rnfor(i=9;i>=0;i--)rnrnif(n>=a[i])rnn-=a[i];rnif(n==0)rnflag=true;rnrnif(flag)cout<<"YES"<<'\n';rnelse cout<<"NO"<<'\n';rnrnsystem("pause");rnreturn 0;rnrnrn不知道哪里错了,求大神指点。。。

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试