zoukankan      html  css  js  c++  java
  • [SinGuLaRiTy] 2017-07-22 NOIP2017 模拟赛

    【SInGuLaRiTy-1029】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.

    <全是看看代码就会的水题,偷个懒不单独写题解了~>

    回文图

    题目描述

    有一片透明玻璃,我们可以在上面涂色。涂色后,你可以对它做两种操作:
    1.旋转,顺时针或逆时针旋转90度;
    2.翻转,水平或垂直翻转180度;
    不管进行多少次旋转或翻转,我们看到都是相同的图形,我们把这样的图形称为"回文图"。
    下图是操作示例。请注意,图中并不是回文图。

         

    现在给你一块n*n的方格状透明玻璃和k种颜色的油漆。请你给每个方格都涂上颜色,涂完后得到一幅回文图。但是这块玻璃上有m个方格事先已被涂上了颜色,你不能修改它们。
    请问,总共能画出多少种不同的回文图?

    输入

    第一行,三个空格间隔的整数n,m,k
    接下来m行,每行两个整数x和y,表示坐标为(x,y)的格子已被涂上了颜色(0<=x,y<n)。

    输出

    一行,一个整数,表示方案总数,结果可能很大,请输出Mod 100 000 007后的结果。

    样例数据

    样例输入1 样例输出1
    3 0 2 8
    样例输入2 样例输出2

    4 2 3
    1 1
    3 1

    3

    <样例解释 for 样例数据1>

    <数据范围>

    对于50%的数据:0<n<=500,0<=m<=100,0<k<=100000
    对于100%的数据:0<n<=10000,0<=m<=2000,0<k<=1000000

    Code

    /*
    对于n*n的方格纸,在格子里填颜色,要满足任意水平、垂直翻转后看到的图形都一样,
    每一个格子,格子旋转翻转后会覆盖八个格子,也就是有八个格子与之对称,所以我们只需考虑左上角的区域,则可填 (n/2+1)*(n/2)/2 种颜色。
    有些方格已经填了颜色,对于已填色的方格,会固定对应格子的颜色,使得可填颜色数减1.注意多个已填色格本来就是同色格,不要多减
    最后,填色方法数为 可用颜色种类k^可填颜色数,用下快速幂即可
    */
    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    struct node{int X,Y;}Used[2001];
    
    const long long Mod=100000007;
    
    bool cmp(node a,node b)
    {
        if(a.X!=b.Y)return a.X<b.X;
        else return  a.Y<b.Y;
    }
    
    long long Montgomery(long long a,long long b,long long c) //快速幂 
    {
          long long ans=1;
          a=a%c;
          while(b>0)
          {                       
                if(b&1)ans=(ans*a)%c;          
                b=b>>1;                                 
                a=(a*a)%c;
          }
          return ans;
    }
    
    int main()
    {
        long long n,m,k,Cnt,T;
        int i,x,y,tmp; 
        scanf("%I64d%I64d%I64d",&n,&m,&k);
        T=(n+1)/2;
        Cnt=(T+1)*T/2;
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            x++; y++;            //题目给出的坐标范围是0到n-1 
            if(x>T)x=n+1-x;      //将已涂色的格子坐标转换成有效区域的坐标(左上角) 
            if(y>T)y=n+1-y;   
            if(y>x){tmp=x; x=y; y=tmp;}
            Used[i].X=x; Used[i].Y=y;
        }
        sort(Used+1,Used+m+1,cmp);
        tmp=0;
        for(i=2;i<=m;i++)
          if(Used[i].X==Used[i-1].X&&Used[i].Y==Used[i-1].Y)tmp++;
        Cnt=Cnt-(m-tmp);
        printf("%I64d
    ",Montgomery(k,Cnt,Mod));
        return 0;
    }

    体检

    题目描述

    开学了,学校要求你进行入学体检。
    你到了校医务室门口,发现有好多学生在排队,人数还在不断增加。
    有多个体检项目要做。每个项目都有很多人在排队。队伍的长度在随着时间变长。该选哪一个队伍排队呢?这成了一个问题。你要安排一下体检顺序,尽可能早的完成所有项目的体检。

    输入

    第一行一个整数n,表示要体检的项目数量
    接下来n行,每行表示一个体检项目。每行两个整数a和b,描述该项目的情况:
    1.如果你在第0时刻加入了这只队伍,需要等待a秒钟才能完成该项目的检查。
    2.当你不在这个队伍里,随时间队伍会变得越来越长,等待的时间每秒钟会增加b秒。

    输出

    一个整数表示你完成体检最短需要花费的时间。
    结果可能很大,所以请mod (365×24×60×60)再打印出结果

    样例数据

    样例输入 样例输出

    5
    1 2
    2 3
    3 4
    4 5
    5 6

    1419

    <样例解释>

    你按以下次序体检.
    1.在第一个队伍中花了1秒
    2.在第二个队伍中花了5秒
    3.在第三个队伍中花了27秒
    4.在第四个队伍中花了169秒
    5.在第五个队伍中花了1217秒
    所以总时间是1419秒.

    <数据范围>

    对于50%的数据有:0<n<=1000   0<=a,b<=30000
    对于100%的数据有:0<n<=100000   0<=a,b<=50000

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    
    using namespace std;
    
    const int maxn=100011;
    const int Mod=365*24*60*60;
    
    struct node{long long a,b;};
    node q[maxn];
    
    int n,i;
    long long ans;
    
    void qsort(int l,int r)
    {
         int x,y;
         long long mid_a,mid_b; 
         node temp;
         x=l;y=r; 
         mid_a=q[(l+r)>>1].a;
         mid_b=q[(l+r)>>1].b;
         while(x<=y)
         {
              while(q[x].a*mid_b<q[x].b*mid_a)x++;
              while(q[y].a*mid_b>q[y].b*mid_a)y--;
              if(x<=y)
              {
                      temp=q[x];
                      q[x]=q[y];
                      q[y]=temp;
                      x++; y--;
              }
         }
         if(l<y)qsort(l,y);
         if(x<r)qsort(x,r);
    }
    
    int main()
    {
              scanf("%I64d",&n);
              for(i=1;i<=n;i++)scanf("%I64d%I64d",&q[i].a,&q[i].b);
              qsort(1,n);
              ans=0;
              for(i=1;i<=n;i++)
                 ans+=(q[i].a+ans*q[i].b)%Mod;
              printf("%I64d
    ",ans%Mod);
              return 0;
    }

    龙珠

    题目描述

    你得到了一个龙珠雷达,它会告诉你龙珠出现的时间和地点。
    龙珠雷达的画面是一条水平的数轴,每一个窗口时间,数轴的某些点上会出现同一种龙珠,每当你获得其中一颗龙珠,其它龙珠就会消失。下一个窗口时间,数轴上又会出现另一种龙珠。总共有n个窗口时间,也就是总共有n种龙珠。
    假设你会瞬间移动,你从数轴的x点移动到y点,耗时0秒,但是需要耗费|x-y|的体力。同时,挖出一颗龙珠也需要耗费一定的体力。请问,最少耗费多少体力,就可以收集齐所有种类的龙珠。

    输入

    第一行,三个整数n,m,x,表示共有n个窗口时间,每个窗口时间会出现m个龙珠,x是一开始你所处的位置。
    接下来有两个n*m的矩阵。
    对于第一个矩阵,坐标为(i,j)的数字表示第i个窗口时间,第j个龙珠的位置。
    对于第二个矩阵,坐标为(i,j)的数字表示第i个窗口时间,挖取第j个龙珠所需的体力。

    输出

    一个整数,表示所需最小体力。

    样例数据

    样例输入 样例输出

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

    8

    <数据范围>

    所有数据均为整数
    数轴范围在0到30000
    挖一颗龙珠所需体力不超过30000
    结果保证在int范围
    对于50%的数据,1<=n<=50,1<=m<=500。
    对于100%的数据,1<=n<=50,1<=m<=5000。

    Code

    /*
    单调队列+DP
    
    设Dp[i][j]表示第i批龙珠中取第j个需要花费的最小体力。
    dp[i][j] = min{ dp[i-1][k] + abs(pos[i-1][k]-pos[i][j]) } + Cost[i][j];
    如果枚举k的话总复杂度位n*n*n,会超时。
    可以看出若每个状态只由上一层位置在其左边的状态的转移而来的话:  
    
    dp[i][j] = min { dp[i-1][k] + pos[i][j] - pos[i-1][k] } + Cost[i][j]
             = min { dp[i-1][k] - pos[i-1][k] } + pos[i][j] + Cost[i][j]
    pos[i-1][k]<=pos[i][j]
    dp[i-1][k]-pos[i-1][k]是个确定的值,就是相当于求位置在pos[i][j]左边的上一层状态中值最小的,可以用个单调队列维护。
    
    由右边转移来的类似,再处理一遍右边转移来的取最优。
    因为要对同一层的点排序,所以总复杂度是n*m*logm。
    */ 
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    
    using namespace std;
    
    const int inf=2000000000;
    
    struct node{int p,v;}Ball[51][5005];//p表示地点,v表示耗费的体力 
    
    int n,m,x,Dp[52][5005],Q[5005];
    
    bool cmp(node a,node b)
    {  return a.p<b.p; }
    
    int Cost(int i,int j,int k)
    {   return Dp[i-1][k]+abs(Ball[i][j].p-Ball[i-1][k].p);  }
    
    int main()
    {int i,j,k,t,head,tail,Ans;
        //输入及初始化 
        scanf("%d%d%d",&n,&m,&x);
        for(i=1;i<=n;i++)
          for(j=1;j<=m;j++)scanf("%d",&Ball[i][j].p);
        for(i=1;i<=n;i++)
          for(j=1;j<=m;j++)scanf("%d",&Ball[i][j].v); 
        //将每一行,按龙珠出现地点由小到大排序 
        for(i=1;i<=n;i++)sort(Ball[i]+1,Ball[i]+m+1,cmp);
         
        //dp
        for(j=1;j<=m;j++)Dp[1][j]=Ball[1][j].v+abs(x-Ball[1][j].p);
        
        for(i=2;i<=n;i++)
        {
           //从左往右
           head=1; tail=0;//tail指向最后一个元素 
           for(k=j=1;j<=m;j++)
           {
                  Dp[i][j]=inf;
                  while(k<=m&&Ball[i-1][k].p<=Ball[i][j].p)//第k个龙珠的位置要不大于第j个龙珠的位置 
                  {
                         t=Cost(i,j,k);
                         while(head<=tail&&Cost(i,j,Q[tail])>=t)tail--;
                         Q[++tail]=k;
                         k++;
                  }
                  if(head<=tail)Dp[i][j]=Cost(i,j,Q[head]);
           }
           //从右往左
           head=1; tail=0;
           for(k=j=m;j>0;j--) 
           {
                  while(k>=1&&Ball[i-1][k].p>Ball[i][j].p)//第k个龙珠的位置要不小于第j个龙珠的位置 
                  {
                          t=Cost(i,j,k);
                          while(head<=tail&&Cost(i,j,Q[tail])>=t)tail--;
                          Q[++tail]=k;  
                          k--;
                  }
                  if(tail>=head)Dp[i][j]=min(Dp[i][j],Cost(i,j,Q[head]));
           } 
           for(j=1;j<=m;j++)Dp[i][j]+=Ball[i][j].v;//这句话必须出现在这里,方便推出下一阶段的状态 
    
        }
        Ans=inf;
        for(j=1;j<=m;j++)if(Dp[n][j]<Ans)Ans=Dp[n][j];
        printf("%d
    ",Ans);
        return 0;
    }

    Time: 2017-07-22

  • 相关阅读:
    集合选数
    二分答案入门乱讲
    浅谈搜索剪枝
    数位DP
    RMQ问题与ST算法
    计数排序与基数排序
    主席树/函数式线段树/可持久化线段树
    树链剖分
    LCA问题
    树的直径、树的重心与树的点分治
  • 原文地址:https://www.cnblogs.com/SinGuLaRiTy2001/p/7221277.html
Copyright © 2011-2022 走看看