day7下

t1
三向城是一个巨大的城市,之所以叫这个名字,是因为城市中遍布着数不尽的三岔路口。(来自取名力为0的出题人)
具体来说,城中有无穷多个路口,每个路口有唯一的一个正整数标号。除了1号路口外,每个路口都连出正好3条道路通向另外3个路口:编号为x(x>1)的路口连出3条道路通向编号为x*2,x*2+1和x/2(向下取整)的3个路口。1号路口只连出两条道路,分别连向2号和3号路口。
所有道路都是可以双向通行的,并且长度都为1。现在,有n个问题:从路口x到路口y的最短路长度是多少?

找最近公共祖先,加深度。

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int M=11000;
int n,ans,x,y;
int main(){
    scanf("%d",&n);
    while(n--){

        scanf("%d%d",&x,&y);
        ans=0;
        int dx=log2(x),dy=log2(y);
        if(dx<dy) swap(dx,dy),swap(x,y);
        while(dx>dy)
            x>>=1,dx--,ans++;

        while(x!=y)
        x>>=1,y>>=1,ans+=2;
        printf("%d\n",ans);
    }
}

t3
你承包了一片香子兰花田。现在到了收获的季节,你需要把种下的香子兰全部收获起来,到花店卖掉并取得新的种子,再向田里播种下一季的香子兰。
你的花田一共由n-2片花田组成,编号从1到n-2。算上你的家和花店,一共有n个地点,其中你的家编号为0,花店编号为n-1。即,家、花田、花店都属于地点,且它们都有一个唯一的0~n-1的编号。有m条双向道路连接这些地点。保证所有地点间都是直接或间接连通的。
你需要从家里出发,经过所有的花田进行收获,再到达花店,再从花店出发经过所有花田进行播种,最后重新回到家中。当你经过一片花田的时候,你可以选择收获、播种或者什么事都不做,也就是说你经过一片未收割的花田时可以不立即收割它,播种亦然。然而,播种必须发生在你完成了所有收获并到花店交货之后。在完成最后一个花田的收获后,你必须在到达花店后才能开始播种。也就是说,在你没有收获完所有花田并到花店交货前,即使你已经经过了花店,你也不能进行播种。(啰嗦了这么多但愿讲明白了)
然而还有一个问题。在收割完花朵后,花田会变得光秃秃的,此时土地里的水分会迅速蒸发。考虑到这个问题,更早被收割的花田也理应更早地被播种。具体来说,你必须保证前个被收割的花田也是前个被播种的,其中符号表示向下取整。你不需要保证这些花田收割和播种的顺序完全一致,而只需要保证前名的集合不变即可。
现在,你需要求出完成上述一系列动作走过的最短路程。
一个状压dp题
dp[i][j]表示从家走,j状态,最后到i的最短路
f[i][j]表示走到花店,从谁开始的最小值
预处理处dp[i][j],f[i][j]
dp[i][j]+f[k][j^1<

#include <cstdio>
#include <cstring>
#include <iostream>
#define inf 707406378
#define N 24
using namespace std;
int n1,n2,e[24],cnt[1050000],n,m,dis[24][24],f[2][24][1050000],ans;
int main(){
    freopen("vanilla_big.in","r",stdin);
    e[0]=1;
    //freopen("vanilla.out","w",stdout);
    for(int i=1;i<=22;i++) e[i]=e[i-1]<<1;
    for(int i=0;i<e[20];i++)
    for(int j=i;j;j>>=1) cnt[i]+=j&1;
    scanf("%d%d",&n,&m);
    n1=(n-2)/2;
    n2=n-2-n1;
    memset(dis,127/3,sizeof dis);
    //printf("%d",dis[1][1]);

    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        ++x,++y;
        dis[y][x]=dis[x][y]=min(dis[x][y],z);
    }
    for(int i=1;i<=n;i++) dis[i][i]=0;
    for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
    if(i!=j&&j!=k&&k!=i)
    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

    if (n == 3)
    {
        printf("%d\n", (dis[1][2]+dis[2][3])*2);
        return 0;
    }
    memset(f,127/3,sizeof f);
    for(int q=0;q<=1;q++)
    {

        for(int i=2;i<n;i++)
        f[q][i][e[i-2]]=dis[(q==0)?1:n][i];

        for(int s=1;s<e[n-2];s++)
        if(cnt[s]<=n1)
        for(int i=2;i<n;i++)
        if(f[q][i][s]<inf)
        for(int j=2;j<n;j++)
        f[q][j][s|e[j-2]]=min(f[q][j][s|e[j-2]],f[q][i][s]+dis[i][j]);

    }

    ans=inf;
    for(int sta=0;sta<e[n-2];sta++)
    if(cnt[sta]==n1){
        int tmp=inf;
        for(int i=2;i<n;i++)
        if(e[i-2]&sta)
        for(int j=2;j<n;j++)
        if(!(e[j-2]&sta))
        tmp=min(tmp,f[0][i][sta]+dis[i][j]+f[1][j][e[n-2]-1-sta]);

        for(int i=2;i<n;i++)
        if(e[i-2]&sta)
        for(int j=2;j<n;j++)
        if(!(e[j-2]&sta))
        ans=min(ans,tmp+f[1][i][sta]+dis[i][j]+f[0][j][e[n-2]-1-sta]);

    }
    printf("%d",ans);   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值