2017.11.02 loli的模拟赛

10 篇文章 0 订阅

noip前8天,loli搞了一场模拟赛,四道题。也训练了一些考试的技能,也发现了一些问题。
%神%%王gay梁%% rank1,AK全场
T1没gang出来,用了topsort却发现不对。水了20分
T2是个比较水的dp,为数组就少开了一个数,拿了90分!!一定要细致细致再细致

T1:
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人的排名仍无法确定。

后来知道这实际上就是传递闭包,用Floyed算法实现。
其实和决斗这道题比较像,都是有传递性的。如果之前的题做好这道题应该不难想出正解:
f[i][j]表示i在j的前面;g[i][j]表示i在j的后面
当某个人前面和后面的人都确定了,那么也就确定了他的名次


#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,x,y,ru[1001];
int zhan[1001],tmp[1001],top,t;
struct Edge{
    int to,next;
}edge[5001];
int ans;
bool f[1001][1001],g[1001][1001];

int main()
{
//  freopen("contest.in","r",stdin);
//  freopen("contest.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1; i<=m; i++)
    {
        scanf("%d%d",&x,&y);
        f[x][y]=true; g[y][x]=true; 
    }
    for (int k=1; k<=n; k++)
        for (int i=1; i<=n; i++)
            for (int j=1; j<=n; j++)
                f[i][j]=f[i][j]||(f[i][k]&&f[k][j]);
    for (int k=1; k<=n; k++)
        for (int i=1; i<=n; i++)
            for (int j=1; j<=n; j++)
                g[i][j]=g[i][j]||(g[i][k]&&g[k][j]);
    for (int i=1; i<=n; i++)
    {
        int qian=0; int hou=0;
        for (int j=1; j<=n; j++)
        {
            if (f[i][j]) qian++;
            if (g[i][j]) hou++;
        }
        if (qian+hou==n-1) ans++;
    }
    printf("%d",ans);
//  fclose(stdin); fclose(stdout);
    return 0;
}

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分钟内爬行,剩余时间用来休息。
很容易看出来是一道dp。
f[i][j]表示第i秒绝望值为j时的走的最大路径。
dp就是考虑这个状态是从上一个状态转移过来的,并且与上一个状态是怎么过来的无关。
dp这个东西,还是要多练啊。
到现在才发现自己刷的题那么少,在保证质量的前提下加大刷题量。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
int n,m,k[10001];
int f[10001][501];

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++) scanf("%d",&k[i]);
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<i; j++)
            f[i][0]=max(f[i][0],max(f[i-j][j],f[i-j][0]));
        for (int j=1; j<=min(i,m); j++)
            f[i][j]=max(f[i][j],f[i-1][j-1]+k[i]);
    }

    printf("%d",f[n][0]);
    return 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

解题报告
%%cansult
暴力(50)不能把road排序,应该扔到tmp里面排序。否则road就改变了
正解:”输出第k+1大的边的最小值”如果看成是最大值最小值问题,那不就是二分吗
二分一条边的长度,看起点到终点的路径中比这条边大的边是否超过了k条。如果大于继续找往小里找
最短路的变形:大于mid就是1,小于mid就是0

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=100001;
const int MAXM=2000001;
int n,m,k,x,y,z;
struct Edge{
    int to,next,dis;
}edge[MAXM];
int team[MAXN],dis[MAXN];
bool b[MAXN];
int ans=-1;

int num_edge,head[MAXN];
void add_edge(int from,int to,int dis)
{
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    edge[num_edge].dis=dis;
    head[from]=num_edge;
}

bool spfa(int x)
{
    memset(dis,0x7f,sizeof(dis));
    int front=0,tail=1;
    team[1]=1; dis[1]=0; b[1]=true;
    while (front<=tail)
    {
        int now=team[++front];
        b[now]=false;
        for (int i=head[now]; i!=0; i=edge[i].next)
        {
            int s=0;
            if (edge[i].dis>x) s=1;
            if (dis[edge[i].to]>dis[now]+s)
            {
                dis[edge[i].to]=dis[now]+s;
                if (!b[edge[i].to])
                {
                    team[++tail]=edge[i].to;
                    b[edge[i].to]=true;
                }
            }
        }
    }
    if (dis[n]<=k) return true;
    else return false;
}

int main()
{
//  freopen("road.in","r",stdin);
//  freopen("road.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    int maxn=0;
    for (int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add_edge(x,y,z); add_edge(y,x,z); maxn=max(maxn,z);
    }
    int l=0; int r=2*maxn;
    while (l<r)
    {
        int mid=(l+r)>>1;
        if (spfa(mid)) {ans=mid; r=mid;}
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}

要不50分代码也扔在这里?

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=1001;
const int MAXM=20001;
int n,m,k,x,y,z;
struct Edge{
    int to,next,dis;
}edge[MAXM];
bool b[MAXN];
int road[MAXN]; int tmp[MAXN];
int ans=0x7fffffff;

int num_edge,head[MAXN];
void add_edge(int from,int to,int dis)
{
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    edge[num_edge].dis=dis;
    head[from]=num_edge;
}

bool comp(int a,int b) {return a>b;}

void work(int tot)
{
//  for (int i=1; i<=tot; i++) printf("%d ",road[i]); printf("\n");
    for (int i=1; i<=tot; i++) tmp[i]=road[i];
    sort(tmp+1,tmp+1+tot,comp);
    ans=min(ans,tmp[k+1]);
}

void dfs(int x,int step)
{
    if (x==n)
    {
        if (step<=k) {printf("0"); exit(0);}
        work(step); return;
    }
    for (int i=head[x]; i!=0; i=edge[i].next)
    {
        if (!b[edge[i].to])
        {
            b[edge[i].to]=true;
            road[step+1]=edge[i].dis;
            dfs(edge[i].to,step+1);
            b[edge[i].to]=false;
        }
    }
}

int main()
{
//  freopen("road.in","r",stdin);
//  freopen("road.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add_edge(x,y,z); add_edge(y,x,z);
    }
//  for (int i=1; i<=num_edge; i++) printf("%d %d\n",edge[i].to,edge[i].dis);
    b[1]=true;
    dfs(1,0);
    if (ans==0x7fffffff)printf("-1");
    else printf("%d",ans);
    return 0;
}

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。

%%%wang gay liang
一道树形dp

/*设F[i][j] ,表示以i为根的子树中断边得到至少一个以j 为节点数的子树的最少断边数量。 
 是不是很长?先慢慢看懂。 
 然后状态转移方程为:
F[i][j]=min(F[p][k]+F[i][j-k]-2) {p为i的子节点}
初始化F[i][1]=与自己相连的边数  
然后有一个细节要注意: 
在子树的信息计算出来后: 
倒着循环j ,不然之前计算的结果会影响后面的答案.*/
//f[i][j]表示在以i为根的子树里截取j个节点最少要删边数
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=201;
const int MAXM=2001;
int n,m,x,y;
struct Edge{
    int to,next;
}edge[MAXM];
int son[MAXN],f[201][201];
int ans=0x7fffffff;

int num_edge,head[MAXN];
void add_edge(int from,int to)
{
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    head[from]=num_edge;
}

void dfs(int x,int fa)
{
    f[x][1]=son[x]-(fa!=0);
    for (int i=head[x]; i!=0 ;i=edge[i].next)
    {
        if (edge[i].to!=fa)
        {
            dfs(edge[i].to,x);
            for (int j=n; j>=1; j--)
                for (int k=1; k<=j; k++)
                    f[x][j]=min(f[x][j],f[edge[i].to][k]+f[x][j-k]-1);
        }
    }
    ans=min(ans,f[x][n]+(fa!=0));
}

int main()
{
    freopen("roads.in","r",stdin);
    freopen("roads.out","w",stdout);
    scanf("%d%d",&m,&n);
    memset(f,1,sizeof(f));
    for (int i=1; i<=m-1; i++)
    {
        scanf("%d%d",&x,&y);
        son[x]++; son[y]++;
        add_edge(x,y); add_edge(y,x);
    }
    dfs(1,0);
    printf("%d",ans);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值