Noip2016 提高组 Day1

T1 玩具迷题

直通

思路:

  1.首先根据数据范围来看,储存小人的姓名开一个二维char数组即可(不会开爆)

  2.然后看他给出的样例以及条件什么的,能够确定出

      ①朝内向右,朝外向左均为+

      ②朝内向左,朝外向右均为-

  但是需要注意的是  加完之后超出最大数n-1(我从0开始进行计数),所以需要进行-n

            减完之后小于0的话,需要进行+n

  3.使问题更简单的方式 (y是移动步数) 

if(y>n)
   y=y%(n+1);

(不懂的话可以手动模拟一下)

上代码:

#include <iostream>
#include <cstdio>
using namespace std;

const int Maxn = 100000;
const int Maxl = 13;
int n,m,len;
bool f[Maxn];
char ssr[Maxn][Maxl];

int main() {
    scanf("%d%d",&n,&m);
    string sr; 
    for(int i=0; i<n; ++i)    {
        scanf("%d",&f[i]);
        cin>>sr;
        len=sr.length();
        for(int j=0; j<len; ++j)
            ssr[i][j]=sr[j];
    }
    int nxt=0;
    for(int i=0,x,y; i<m; ++i) {
        scanf("%d%d",&x,&y);
        if(y>n)
            y=y%(n+1);
        if(x) {//right
            if(f[nxt]==0) //朝内 
                nxt+=y;
            else //朝外 
                nxt-=y;
        }
        else {//left
            if(f[nxt]==0) //朝内 
                nxt-=y;
            else //朝外 
                nxt+=y;
        }
        if(nxt<0)
            nxt+=n;
        if(nxt>=n)
            nxt-=n;
    }
    for(int i=0; ; ++i) {
        if(ssr[nxt][i])
            printf("%c",ssr[nxt][i]);
        else
            break;
    }
    return 0;
}
View Code

T2 天天爱跑步

直通

思路:

  首先看到他给出这么详细的数据范围以及约定,不打一下暴力简直就是对不起他啊!所以首先我们这道题可以打暴力!

  ①首先来看前4个点中的前两个点的约定是起点等于终点,他的意思就是说明每一个点只存在于0秒的时候。

   而另外两个点的约定是所有的Wj均为0,所以这四个点再输入u,v的时候不用存边,只需要把起点终点以及每个点的Wj即可。

  具体的方法就是从1到m循环一遍若当前点的times为0,ans[该点的初始时间]++,最后按顺序输出ans数组即可

  核心代码如下:
  if(flag1||flag2) { //所有点的起点与终点相同 || 所有的Wj==0
        for(int i=1,u,v; i<n; ++i)
            scanf("%d%d",&u,&v);
        for(int i=1; i<=n; ++i)
            scanf("%d",&times[i]);
        for(int i=1; i<=m; ++i)
            scanf("%d%d",&t[i].si,&t[i].ti);
        for(int i=1; i<=m; ++i) {
            if(!times[t[i].si])
                ans[t[i].si]++;
        }
        for(int i=1; i<=n; ++i)
            printf("%d ",ans[i]);
        return 0; 
    }

  ②然后我们来看第五个点,没有任何约定!但是看看数据范围跟前四个是一样的。

  等等!如果说数据范围的话来。。。。这个数据范围辣么小,眼前一亮!其实这不就是个搜索嘛!

  (这样子就可以将前五个点连接起来写一个解题方法了!)

  搜索思路:

    对于题目中给出的m个人的起止点进行dfs,记录下来他当前花费的时间以及他当前的父节点(以便于寻找并更新他的子节点)

    又因为在dfs中具有回退过程,若已经找到了正确的路线(即找到了终点now==end)后,

    可能还会再次进行搜索另外一些没有被搜索过,但是没有什么用的点(其实说白了就是在浪费时间),

    所以我们需要在回退过程中在进行更新p数组

  核心代码如下:(亲测搜索比特判还要快qwq,差不多快100多ms)
void dfs(int now,int ed,int time,int pre) {
    if(now==ed) {
        ok=true;
        p[now][time]++;
        return ;
    }
    if(ok) return ; //防止找到正确的路线之后在回退的过程中再次进行搜索一些无用的点,所以直接return 
    for(int i=head[now],v; i; i=e[i].next) {
        v=e[i].to;
        if(v==pre) continue; //不是子节点 
        dfs(v,ed,time+1,now); //更新now,time,pre并继续搜索下去 
        if(ok) 
        {
            p[now][time]++; //将正确的路线上的点的p进行更新 
            return;
        }
    }
}

  ③6.7.8个点:如果一棵树树退化成一条链的话,那么这个点上的观察员,

  就只能够看到从i+-times[i]这两个位置出发的人的就只需要进行加减再加上特判他存在即可

  我们需要知道:

    如果i能看到起点深度比他小的点,那么这个点的终点需要>=i

    如果i能看到起点深度比他大的点,那么这个点的终点需要<=i

  核心代码如下:
    for(int i=1; i<=n; ++i) {
            if(i-times[i]>0) //若往前找有可能能够观察到人 
                for(int j=head[i-times[i]],v; j; j=e[j].next) {
                    v=e[j].to; //能够成功被观察到的这个点的编号为v(通过链表进行寻找) 
                    if(i<=t[v].ti) //是从v点往后找,所以v点的目的地必须要大于i才可以,不然走不到i点 
                        ans[i]++;
                }
            if(i+times[i]<=n) { //若往后找有可能能够观察到人 
                for(int j=head[i+times[i]],v; j; j=e[j].next) {
                    v=e[j].to;
                    if(i>=t[v].ti) //同理,因为上一种情况是从v点往后找,所以v点的目的地必须大于i,这一种情况则相反(小于) 
                        ans[i]++;
                }
            }
        }

  ④9-12点:所有的点的起点相同

    我们若设1号的深度是0,那么只有当那些观察员们所在节点的深度与当前这个节点的Wi是相同的,他才有可能能够观察到人。

    若不相等的话,直接输出0即可

    若相等的话,那么他就一定能够观察到所有经过他的人,也就是说以i为根的子树中拥有终点的个数有多少个

    所以首先可以bfs搜索出所有点的深度然后用一个简单的dfs来统计每一个节点的子树中拥有终点的个数(用son数组记录下来)

  核心代码如下:
void bfs() {
    queue<Flag5>q;
    cur.dep=0; cur.id=1; dad[1]=0;
    q.push(cur);
    while(!q.empty()) {
        cur=q.front();
        q.pop();
        int u=cur.id;
        for(int i=head[cur.id],v; i; i=e[i].next) {
            v=e[i].to;
            if(v==dad[u]) continue;
            dad[v]=u; nxt.dep=cur.dep+1;
            nxt.id=v; deeps[v]=deeps[u]+1;
            q.push(nxt);
        }
    }
}

void Dfs(int u) {
    son[u]=sum[u];
    for(int i=head[u],v; i; i=e[i].next) {
        v=e[i].to;
        if(v==dad[u]) continue;
        Dfs(v);
        son[u]+=son[v];
    }
}

  至于正解什么的,待研究中。。。

神奇的第一反映233:

  天天就是天天。

上代码:

①暴力代码小汇总 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
using namespace std;

const int N = 1000;
const int Maxn = 100000;
int n,m;
int times[Maxn],ans[Maxn];
struct B {
    int next,to;
}e[Maxn<<1];
int top,head[Maxn];
inline void add(int u,int v) {
    top++;e[top].to=v;
    e[top].next=head[u];head[u]=top;
}

bool flag,ok,flag1,flag2,flag4,flag5;
int p[N][N],sum[Maxn],deeps[Maxn],dad[Maxn],son[Maxn];
struct Flag4 {
    int si,ti;
}t[Maxn];
struct Flag5 {
    int dep,id;
}cur,nxt;

void dfs(int now,int ed,int time,int pre) {
    if(now==ed) {
        ok=true;
        p[now][time]++;
        return ;
    }
    if(ok) return ; //防止找到正确的路线之后在回退的过程中再次进行搜索一些无用的点,所以直接return 
    for(int i=head[now],v; i; i=e[i].next) {
        v=e[i].to;
        if(v==pre) continue; //不是子节点 
        dfs(v,ed,time+1,now); //更新now,time,pre并继续搜索下去 
        if(ok) 
        {
            p[now][time]++; //将正确的路线上的点的p进行更新 
            return;
        }
    }
}

void bfs() {
    queue<Flag5>q;
    cur.dep=0; cur.id=1; dad[1]=0;
    q.push(cur);
    while(!q.empty()) {
        cur=q.front();
        q.pop();
        int u=cur.id;
        for(int i=head[cur.id],v; i; i=e[i].next) {
            v=e[i].to;
            if(v==dad[u]) continue;
            dad[v]=u; nxt.dep=cur.dep+1;
            nxt.id=v; deeps[v]=deeps[u]+1;
            q.push(nxt);
        }
    }
}

void Dfs(int u) {
    son[u]=sum[u];
    for(int i=head[u],v; i; i=e[i].next) {
        v=e[i].to;
        if(v==dad[u]) continue;
        Dfs(v);
        son[u]+=son[v];
    }
}

int main() {
    freopen("runninga.in","r",stdin);
    freopen("runninga.out","w",stdout);
    scanf("%d%d",&n,&m);
    if(n%10<=3) flag=true;
//    if(n%10==1) flag1=true;
//    if(n%10==2) flag2=true;
    if(n%10==4) flag4=true;
    if(n%10==5) flag5=true;
    if(flag) {
        for(int i=1,u,v; i<n; ++i) {
            scanf("%d%d",&u,&v);
            add(u,v),add(v,u);
        }
        for(int i=1; i<=n; ++i)
            scanf("%d",&times[i]);
        for(int i=1,u,v; i<=m; ++i) {
            scanf("%d%d",&u,&v);
            ok=false;
            dfs(u,v,0,0);
        }
        for(int i=1; i<=n; ++i)
            printf("%d ",p[i][times[i]]);
        return 0;
    }
    /*
    if(flag1||flag2) { //所有点的起点与终点相同 || 所有的Wj==0
        for(int i=1,u,v; i<n; ++i)
            scanf("%d%d",&u,&v);
        for(int i=1; i<=n; ++i)
            scanf("%d",&times[i]);
        for(int i=1; i<=m; ++i)
            scanf("%d%d",&t[i].si,&t[i].ti);
        for(int i=1; i<=m; ++i) {
            if(!times[t[i].si])
                ans[t[i].si]++;
        }
        for(int i=1; i<=n; ++i)
            printf("%d ",ans[i]);
        return 0; 
    }
    */
    if(flag4) { //关系退化为一条链 
        for(int i=1,u,v; i<n; ++i)
            scanf("%d%d",&u,&v);
        for(int i=1; i<=n; ++i)
            scanf("%d",&times[i]);
        for(int i=1; i<=m; ++i) {
            scanf("%d%d",&t[i].si,&t[i].ti);
            add(t[i].si,i); //将该点的初始时间与该点的编号进行连接 
        }
        for(int i=1; i<=n; ++i) {
            if(i-times[i]>0) //若往前找有可能能够观察到人 
                for(int j=head[i-times[i]],v; j; j=e[j].next) {
                    v=e[j].to; //能够成功被观察到的这个点的编号为v(通过链表进行寻找) 
                    if(i<=t[v].ti) //是从v点往后找,所以v点的目的地必须要大于i才可以,不然走不到i点 
                        ans[i]++;
                }
            if(i+times[i]<=n) { //若往后找有可能能够观察到人 
                for(int j=head[i+times[i]],v; j; j=e[j].next) {
                    v=e[j].to;
                    if(i>=t[v].ti) //同理,因为上一种情况是从v点往后找,所以v点的目的地必须大于i,这一种情况则相反(小于) 
                        ans[i]++;
                }
            }
        }
        for(int i=1; i<=n; ++i)
            printf("%d ",ans[i]);
        return 0; 
    } 
    if(flag5) { //所有人的起点均相同,且为1 
        for(int i=1,u,v; i<n; ++i) {
            scanf("%d%d",&u,&v);
            add(u,v),add(v,u);
        }
        for(int i=1; i<=n; ++i)
            scanf("%d",&times[i]);
        for(int i=1,u,v; i<=m; ++i) {
            scanf("%d%d",&u,&v);
            sum[v]++;
        }
        bfs();
        Dfs(1);
        for(int i=1; i<=n; ++i)
            if(times[i]==deeps[i])
                printf("%d ",son[i]);
            else
                printf("0 ");
    }
    return 0;
}
View Code

②咦?正解呢?嘻嘻,还不会啦!


T3 换教室

直通

思路:

  这是一道概率DP的题

  首先我们用floyd求两教室间最短路,这个不用说,三个for循环即可

  然后我们可以用f[i,j,k] 表示前i个时间段申报j个。

  其中:k=0表示当前时间段没选,k=1表示当前时间段选了

上代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define INF 0x7fffffff
using namespace std;

const int Mn = 2002;
const int Mv = 303 ;
int n,m,v,e;
int c[Mn],d[Mn],map[Mv][Mv];
double ans=20020811.233,tmp;
double k[Mn],f[Mn][Mn][2];
//f[i][j][k]表示前i个时间段申报j个.
//k=0表示当前时间段没选,k=1表示当前时间段选了

void pre() {
    //这个初始化有点坑说实话... 
    memset(map,0x3f,sizeof(map));
    for(int i=0; i<Mv; ++i) map[i][i]=0;
    /*--------------------------------*/
    for(int i=0; i<Mn; ++i)
        for(int j=0; j<Mn; ++j)
            f[i][j][1]=f[i][j][0]=INF;
}

void floyd() {
    for(int k=1; k<=v; ++k) 
        for(int i=1; i<=v; ++i)
            for(int j=1; j<=v; ++j)
                if(map[i][k]+map[k][j]<map[i][j])
                    map[i][j]=map[i][k]+map[k][j];
}

void dp() {
    f[1][0][0]=f[1][1][1]=0;
    for(int i=2; i<=n; ++i) { //从第二天开始换教室
        for(int j=0; j<=i && j<=m; ++j) { //开始dp选取换第几个教室 
            double tmp0=f[i][j][0];
            f[i][j][0]=
             min(f[i-1][j][0]+map[c[i-1]][c[i]], //上一天不换并且今天也不换,所以直接加上两教室之间的距离(直接走过去)即可 
              f[i-1][j][1] //上一天换 
               +map[c[i-1]][c[i]]*(1.0-k[i-1]) //申报了但是申请不成功的概率 
               +map[d[i-1]][c[i]]*k[i-1]); //申报了并且申请成功的概率 
            f[i][j][0]=min(tmp0,f[i][j][0]); //跟没有更新之前的进行比较取最优值
            if(j) { //如果能申报
                double tmp1=f[i][j][1];
                f[i][j][1]=
                 min(f[i-1][j-1][0] //前i-1天申报j-1(因为当天已经确定申报)次 
                  +k[i]*map[c[i-1]][d[i]] //当天申报成功
                  +(1.0-k[i])*map[c[i-1]][c[i]], //当天申报不成功 
                 f[i-1][j-1][1] //同上 
                  +(1.0-k[i-1])*k[i]*map[c[i-1]][d[i]] //当天申报成功,上一天申报不成功
                  +k[i-1]*k[i]*map[d[i-1]][d[i]] //当天申报成功,上一天申报也成功
                  +k[i-1]*(1.0-k[i])*map[d[i-1]][c[i]] //当天申报不成功,上一天申报成功
                  +(1.0-k[i-1])*(1.0-k[i])*map[c[i-1]][c[i]] //当天申报不成功,上一天申报也不成功
                    );
                f[i][j][1]=min(f[i][j][1],tmp1); //跟没有更新之前的进行比较取最优值 
            }
        }
    }
}

int main() {
//    freopen("classrooma.in","r",stdin);
//    freopen("classrooma.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&v,&e);
    for(int i=1; i<=n; ++i) scanf("%d",&c[i]);
    for(int i=1; i<=n; ++i) scanf("%d",&d[i]);
    for(int i=1; i<=n; ++i) scanf("%lf",&k[i]);
    pre();
    for(int i=1,a,b,w; i<=e; ++i) {
        scanf("%d%d%d",&a,&b,&w);
        if(w<map[a][b]) map[a][b]=map[b][a]=w;
    }
    floyd();
    dp();
    for(int i=0; i<=m; ++i)
        tmp=min(f[n][i][0],f[n][i][1]),ans=min(ans,tmp);
    printf("%.2lf\n",ans);
    return 0;
} 
View Code

 

posted @ 2017-09-10 09:15  夜雨声不烦  阅读(152)  评论(0编辑  收藏  举报