【loli的胡策】高一信心场11.2(bitset+dp+二分+树形背包)

本文介绍了四道编程竞赛题目,涉及比赛排名、运动计划、找路径和道路问题的解决方案。通过bitset、动态规划和二分搜索等算法,解析了如何解决这些实际问题。
摘要由CSDN通过智能技术生成

1、比赛(contest)

N(1 <= N <= 100)个同学按1..N依次编号参加羽毛球比赛。每人的实力不尽相同,不存在两个人实力相同的情况。
比赛分成若干轮进行,每一轮是两个人对决。如果编号为A的同学的实力强于编号为B的同学(1 <= A <= N; 1 <= B <= N; A != B),那么她们的对决中,编号为A的同学总是能胜出。
你为了知道实力的具体排名,于是你找来M(1 <= M <= 4,500)轮比赛的结果,希望能根据这些信息,推断出尽可能多的同学得实力排名。比赛结果保证不会自相矛盾。

输入格式:

第1行:用空格隔开的2个整数:N 和 M
第2..M+1行: 每行2个整数A、B(用空格隔开的),描述一轮比赛,每行的第一个编号为胜者

输入样例:

5 5
4 3
4 2
3 2
1 2
2 5

输出格式:

1个整数,表示排名可确定的同学的数目

输出样例:

2

输出说明:

编号为2的同学的排名为第4,编号为5的同学排名最后。其他3人的排名仍无法确定。

题解:

啊这个题一眼秒?top原图+逆图一波然后bitset乱搞?

代码:

#include <queue>
#include <bitset>
#include <cstdio>
#include <cstring>
#define N 105
#define M 4505
using namespace std;
int n,m,i;
bitset<105>in[N],out[N];bool vis[N];
int tot,nxt[M],point[N],v[M],tot1,nxt1[M],point1[N],v1[M],du[N],du1[N];
void addline(int x,int y){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}
void addline1(int x,int y){++tot1; nxt1[tot1]=point1[x]; point1[x]=tot1; v1[tot1]=y;}
void top()
{
    int i;
    queue<int>q;
    for (i=1;i<=n;i++) 
    {
        if (!du[i]) q.push(i);
        in[i][i]=1;
    }
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        vis[now]=1;
        for (int i=point[now];i;i=nxt[i])
        {
            in[v[i]]|=in[now];
            du[v[i]]--;
            if (!du[v[i]] && !vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
        }
    }
    memset(vis,0,sizeof(vis));
    for (i=1;i<=n;i++) 
    {
        if (!du1[i]) q.push(i);
        out[i][i]=1;
    }
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        vis[now]=1;
        for (int i=point1[now];i;i=nxt1[i])
        {
            out[v1[i]]|=out[now];
            du1[v1[i]]--;
            if (!du1[v1[i]] && !vis[v1[i]]) vis[v1[i]]=1,q.push(v1[i]);
        }
    }
}
int main()
{
    freopen("contest.in","r",stdin);
    freopen("contest.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addline(x,y);du[y]++;
        addline1(y,x);du1[x]++;
    }
    top();
    int ans=0;
    for (i=1;i<=n;i++)
      if (in[i].count()+out[i].count()-1==n) ans++;
    printf("%d",ans);
}

2、运动计划(run)

地鼠误入一个隧道它必须在N(1 <= N <= 10,000)分钟内离开,否则隧道就会被人注满水,在每分钟的开始,地鼠会选择下一分钟是用来爬行还是休息。 地鼠的体力限制了她爬行的距离。更具体地,如果地鼠选择在第i分钟内爬行,她可以在这一分钟内爬可k_i(1 <= k_i <= 1,000)米,并且她的绝望指数会增加1。不过,无论何时地鼠的绝望指数都不能超过M(1 <= M <= 500)。如果地鼠选择休息,那么她的绝望指数就会每分钟减少1,但她必须休息到绝望指数恢复到0为止。在绝望指数为0时休息的话,绝望指数不会再变动。逃离开始时,地鼠的绝望指数为0。还有,在N分钟的逃离结束时,地鼠的绝望指数也必须恢复到0,否则她将对自己的一生失去信心。请你计算一下,地鼠最多能爬多少米。(已知地鼠爬行距离达到最大时恰好能逃离隧道。

输入格式:

第1行: 2个整数:N 和 M(用空格隔开)
第2。。N+1行: 第i+1行为1个整数:k_i

输入样例 (run.in):

5 2
5
3
4
2
10

输出格式:

输出一个整数,表示地鼠能爬行的最大距离

输出样例 (run.out):

9

输出说明:

第1分钟内爬行,在第2分钟内休息,在第3分钟内爬行,剩余时间用来休息。

题解:

以前好像做过这道题
第一次写挂了20pts?被学弟踩
尽量避免让状态向未来转移啊!
dp的状态很好设计:dp[i][j]表示时间i绝望值为j的最远距离

代码:

#include <cstdio>
#include <iostream>
using namespace std;
int f[10005][505],k[10005];
int main()
{
    freopen("run.in","r",stdin);
    freopen("run.out","w",stdout);
    int n,m,i,j,l;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++) scanf("%d",&k[i]);
    for (i=1;i<=n;i++)
    {
        f[i][0]=max(f[i][0],f[i-1][0]);
        for (j=m;j>=1;j--)
        {
          f[i][j]=max(f[i][j],f[i-1][j-1]+k[i]);
          if (i>j) f[i][0]=max(f[i][0],f[i-j][j]);
        } 
    }
    printf("%d",f[n][0]);
}

3、找路径(road)

图上有N(1 <= N <= 1,000)个点,一共m(1 <= m<= 10,000)条边。 第i条边的端点为s_i、t_i,长度为L_i (1 <= L_i <= 1,000,000)。数据中保证每对{s_i,t_i}最多只出现1次。你的任务是找一条将点1和点N连起来的路径,如果存在不超过k(0=< k<=n)条边的路径则输出0。否则输出第k+1大的边的最小值.

输入格式:

第1行: 3个整数:N m K(用空格隔开)
第2..m+1行: 第i+1行为3个整数:s_i t_i L_i(用空格隔开)

输入样例

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

输出格式:

1个整数

输出样例

4

输出说明:

路径为:1->3->2->5

题解:

这个一开始以为是原题,只不过那个题让求最小距离,这个让求最大值的最小值
熟悉不?二分!
可以转化为最短路,比mid长的长度设为1,其他设为0,如果最短路小于等于k,说明这个值ok啊
题目没说双向边差评!题目不给不连通-1差评!

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#define N 1005
#define M 30005
#define INF 1e9
using namespace std;
struct hh{int maxx,k;};
int tot,nxt[M],point[N],v[M],c[M],dis[N],k,n,m;
bool vis[N],vv=0;
void addline(int x,int y,int z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
} 
void spfa()
{
    queue<int>q;
    q.push(1);
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=0;
    while (!q.empty())
    {
        int now=q.front();q.pop();
        for (int i=point[now];i;i=nxt[i])
          if (dis[v[i]]>dis[now]+1)
          {
            dis[v[i]]=dis[now]+1;
            if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
          }
    }
    if (dis[n]>INF) printf("-1"),vv=1;
    else if (dis[n]<=k) printf("0"),vv=1;
}
bool check(int mid)
{
    queue<int>q;
    q.push(1);
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=0;
    while (!q.empty())
    {
        int now=q.front();q.pop();vis[now]=0;
        for (int i=point[now];i;i=nxt[i])
          if (c[i]<=mid) 
          {
            if (dis[v[i]]>dis[now]) 
            {
                dis[v[i]]=dis[now];
                if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
            }
          }
          else 
            if (dis[v[i]]>dis[now]+1)
            {
                dis[v[i]]=dis[now]+1;
                if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
            }
    }
    if (dis[n]>k) return 0;
    return 1;
}
int main()
{
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
    int l=INF,r=0;
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        addline(x,y,z);r=max(r,z);l=min(l,z);
    }
    spfa();
    if (vv) return 0;
    int ans=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (check(mid)) ans=mid,r=mid-1;
        else l=mid+1;  
    }
    printf("%d",ans);
}

4、道路(roads)

有一棵树,有m(1到150)个节点,如果砍掉几条边这棵树就会变成多棵树,求最少砍掉几条边可以在得到的树中至少有一颗节点个数为n(1到m)。

输入说明:

第1行 m n , 第2到第m行,每行两个整数a 和b,表示a是b的父结点(1号点为根节点)。

输出说明:

一个数为最少砍掉的边数。

输入样例:

11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11

输出样例:

2

样例说明:

砍掉1—4和1—5。

题解:

好像是原题
f[i][j]表示以i为根节点分离出来j个点的最少切割数量

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 200
using namespace std;
const int INF=1e9;
int tot,nxt[N*2],point[N],v[N*2],size[N],f[N][N],n,ans,m,du[N];
void addline(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void treedp(int x,int fa)
{
    f[x][1]=du[x];
    for (int i=point[x];i;i=nxt[i]) 
      if (v[i]!=fa)
      {
        treedp(v[i],x);
        for (int j=m;j>=1;j--)
          for (int k=1;k<=j;k++)
            f[x][j]=min(f[x][j],f[v[i]][k]+f[x][j-k]-2);
      }
    ans=min(ans,f[x][m]);
}
int main()
{
    freopen("roads.in","r",stdin);
    freopen("roads.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addline(x,y);du[x]++;du[y]++;
    }
    memset(f,0x3f,sizeof(f));
    ans=INF;
    treedp(1,0);
    printf("%d",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值