2022“杭电杯”中国大学生算法设计超级联赛(5)

目录

1003.Slipper

1010.Bragging Dice

1012.Buy Figurines


1003.Slipper

题意:有一棵树,树上路径有边权.我们可以花费p的代价,从x走到x+k或者x-k,给出起点终点求最短距离。

思路:一眼dij+建立虚点.可是这个虚点建立会有一点问题.我一开始是对于x+k和x-k向着x建议一条边.这里就会出现问题,这样的话进行缩点的操作,同层的点也可以互相到达了(用过这一层到x在到这一等的另一个点),此时考虑进行点的拆分,如果我们把上述的每一个虚拟节点拆分成两个节点,一个出节点一个入节点,(x也要进行拆分),这样就不会让同层节点相互链接,因为这样要保证每个点的出节电和入节点相连接才能互相连接,而之后的操作不会吧无关的出入点相连.所以成立.

#include<bits/stdc++.h>
using namespace std;
int h[4000010],vis[4000010],tot=0,d[4000010],n,p,k,s,t;
long long dis[4000010];
vector<int>vv[4000010];
struct node
{
    int to,ne,val;
}edge[2*4000010];
struct poi
{
    int x;
    long long len;
    bool operator<(poi c)const{
        return c.len<len;      //从小到大排序
    }
};
void add(int x,int y,int w)
{
    edge[++tot].to=y;
    edge[tot].val=w;
    edge[tot].ne=h[x];
    h[x]=tot;
    return ;
}
void dfs(int x,int fa)
{
    d[x]=d[fa]+1;
    vv[d[x]].push_back(x);
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int j=edge[i].to;
        if(j==fa)
            continue;
        dfs(j,x);
    }
    return ;
}
void dij()
{
    memset(vis,0,sizeof vis);
    memset(dis,0x3f,sizeof dis);
    priority_queue<poi>qu;
    qu.push({s,0});
    dis[s]=0;
    while(qu.size())
    {
        poi bef=qu.top();
        qu.pop();
        if(vis[bef.x]==1)
            continue;
        vis[bef.x]=1;
        for(int i=h[bef.x];i!=-1;i=edge[i].ne)
        {
            int val=edge[i].val,to=edge[i].to;
            if(dis[to]>val+bef.len)
            {
                dis[to]=val+bef.len;
                qu.push({to,dis[to]});
            }
        }
    }
    return ;
}
void solve()
{
    tot=0;
    int u,v,w;
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++) 
        vv[i].clear();
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }  
    scanf("%d%d%d%d",&k,&p,&s,&t);
    dfs(1,0);
    for(int i=1;i<=n;i++)
	{//+2*n是进入,+n是出
		add(i,d[i]+n,0);
		add(d[i]+2*n,i,0);
		if(d[i]-k>0)
            add(d[i]+n,d[i]-k+2*n,p);
		if(d[i]+k<=n)
            add(d[i]+n,d[i]+k+2*n,p);
	}
    dij();
    printf("%lld\n",dis[t]);
    return ;
}
signed main()
{
    int t;
    scanf("%lld",&t);
    while(t--)
       solve();
    return 0;
}

1010.Bragging Dice

题意:摇骰子游戏,每人n个骰子,摇出点数后.某一个人先手他会说某个数有几个.这个数量就是两个人该点数的骰子数量总和,如果后手不相信,可以开骰.如果数量符合那么后手失败,否则后手成功.

思路:喝酒玩这个,按照题目意思,两者都知道了对方骰子,那肯定先手会胜利,因为都已经互相知道了.但是有一个特殊规则,题目补充了,某个人每个骰子的点数都不一样,那么他的所有点数都为0.当两者都是这种情况,那么点数都为0,但是先手不能说0,所以后手必胜,其余先手必胜.

#include<bits/stdc++.h>
using namespace std;
map<int,int>m1,m2;
void solve()
{
    m1.clear(),m2.clear();
    int n,x;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&x),m1[x]++;
    for(int i=1;i<=n;i++)
        scanf("%d",&x),m2[x]++;
    int f=0;
    for(int i=1;i<=6;i++)
    {
        if(m1[i]>1||m2[i]>1)
            f=1;
    }
    if(f==1)
        printf("Win!\n");
    else
        printf("Just a game of chance.\n");
    return;
}
signed main()
{
    int t;
    scanf("%d",&t);
    while(t--)
        solve();
    return 0;
}

1012.Buy Figurines(线段树二分+优先队列)

题意:有n个人来m个水龙头处打水,已知每个人的来的时间和把水桶装满的时间,每个人都会优先选择人少的水龙头处排队.问最少要花费多久这些人把水桶接满.

思路:针对于这个问题,东北省赛做过类似的题.我们考虑如何才能最快的知道哪个水龙头此时的人最少.可以考虑用线段树维护一个当前区间内人最少有多少个在排队.每个人过来排队时去进行更新.首先在线段树进行查找,选取左右区间中最小值更小的那个区间进行深入查找,这样就可以找到人最少的那个水龙头,我们在对于进来接水的人用一个优先队列去维护,里面维护的值就是每个人的序号和接水结束的时间(按照时间排序的小顶堆).当每个新人来的时候我们把这个人来的时间内接完水的人全部清除(此时每个人要按照来的时间排序,从小到大,清除的话在线段树找到对应序号的单点-1即可),当把那些要走的人清除后再把新人加入优先队列.(ps:因为要考虑结束时间,所以要记录一下每个水龙头当前要在什么时候没有人.)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =2e5+10,mod=998244353;
int ent[N];
struct node
{
    int minn,num,l,r;
}tr[4*N];//这棵树的含义是该水龙头有多少人,num为人数,minn为区间最小值
struct peo
{
    int idx,time;
    bool operator<(const peo b)const{
        return this->time>b.time;
    }
};
struct peo11
{
    int st,len;
}peo1[N];
bool cmp(peo11 a,peo11 b)
{
    if(a.st!=b.st)
        return  a.st<b.st;
    else
        return a.len<b.len;
}
priority_queue<peo>qu;
void build(int l,int r,int node)
{
    tr[node].l=l;
    tr[node].r=r;
    tr[node].minn=0;
    tr[node].num=0;
    if(l==r)
        return ;
    int mid=(l+r)/2;
    build(l,mid,node<<1);
    build(mid+1,r,node<<1|1);
    return ;
}
void modify(int node,int st,int en)
{
    int l=tr[node].l,r=tr[node].r;
    if(l==r)
    {
        tr[node].num++;
        tr[node].minn=tr[node].num;
        qu.push({l,(max(ent[l],st)+en)});
        ent[l]=max(ent[l],st)+en;//更新当前水龙头什么时候没人
//需要注意的是下一个人可能在没人之后几分钟才来接水,也可能直接排在别人后面
//所以这里要取max
        return ;
    }
    int mid=(l+r)/2;
    if(tr[node<<1].minn<=tr[node<<1|1].minn)
        modify(node<<1,st,en);
    else 
        modify(node<<1|1,st,en);
    tr[node].minn=min(tr[node<<1].minn,tr[node<<1|1].minn);
    return ;
}
void update(int node,int idx)
{
    int l=tr[node].l,r=tr[node].r;
    if(l==r)
    {
        tr[node].num--;
        tr[node].minn=tr[node].num;
        return ;
    }
    int mid=(l+r)/2;
    if(l<=idx&&idx<=mid)
        update(node<<1,idx);
    else if(mid<idx&&idx<=r)
        update(node<<1|1,idx);
    tr[node].minn=min(tr[node<<1].minn,tr[node<<1|1].minn);
}

void solve()
{
    int n,m;
    scanf("%lld%lld",&n,&m);//输入n,m,n个人和m个水龙头
    build(1,m,1);//对于水龙头建树
    for(int i=1;i<=m;i++)
        ent[i]=0;
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&peo1[i].st,&peo1[i].len);
    sort(peo1+1,peo1+1+n,cmp);
    
    for(int i=1;i<=n;i++)
    {
        while(qu.size()&&qu.top().time<peo1[i].st)
        {
            update(1,qu.top().idx);
            qu.pop();
        }
        modify(1,peo1[i].st,peo1[i].len);
    }
    while(qu.size())
    {
        update(1,qu.top().idx);
        qu.pop();
    }
    int ans=-1;
    for(int i=1;i<=m;i++)
    {
        ans=max(ans,ent[i]);
    }
    printf("%lld\n",ans);
    return ;
}
signed main()
{
    int t;
    scanf("%lld",&t);
    while(t--)
        solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值