zoukankan      html  css  js  c++  java
  • 冲刺Noip2017模拟赛5 解题报告——五十岚芒果酱

    1. 公约数(gcd)

    【问题描述】
    给定一个正整数,在[1,n]的范围内,求出有多少个无序数对(a,b)满足
    gcd(a,b)=a xor b。
    【输入格式】
    输入共一行,一个正整数n。
    【输出格式】
    输出共一行,一个正整数表示答案。
    【输入输出样例】
    gcd .in gcd .out
    3 1
    解释:只有(2,3)满足要求
    【数据范围】
    对于30%的数据满足n<=1000
    对于60%的数据满足n<=10^5
    对于100%的数据满足n<=10^7
    题目

     tag:数学

    思路:就一个等式,gcd(a,b)==d==a^b,我们要把它扩展开来,由于a==b无解,设a严格大于b,d|a,d|b==〉d|(a-b),则a-b>=d=gcd(a,b)。a^b显然>=a-b,因为每一位异或的结果要么比减法大要么跟减法一样。最后得gcd(a,b)<=a-b<=a^b。现在看需要枚举啥子,取特 gcd(a,b)=a-b=a^b=d,运用异或的性质得a^d=b代回去,a-a^d=d,移项a-d=a^d,枚举d和d的倍数即可。这个算法的时间复杂度是O(nlogn)。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 #include<algorithm>
     5 using namespace std;
     6 int n,ans;
     7 int main()
     8 {
     9     //freopen("gcd.in","r",stdin);
    10     //freopen("gcd.out","w",stdout);
    11     scanf("%d",&n);
    12     for(int i=1;i<=n;++i)
    13         for(int j=n/i;j>=2;--j){
    14             int a=i*j;
    15             if((a-i)==(a^i)) ans++;
    16         }
    17     printf("%d",ans);
    18     return 0;
    19 }

    2. 通讯(message)

    【问题描述】“这一切都是命运石之门的
    选择。”
    试图研制时间机器的机关 SERN 截获了中二科学家伦太郎发往过去的一条
    短信,并由此得知了伦太郎制作出了电话微波炉(仮)。
    为了掌握时间机器的技术,SERN 总部必须尽快将这个消息通过地下秘密通讯
    网络,传达到所有分部。
    SERN 共有 N 个部门(总部编号为 0),通讯网络有 M 条单向通讯线路,每条
    线路有一个固定的通讯花费 Ci。
    为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向
    另一个与它有线路的部门传递( 可能存在多条通信线路)。我们定义总费用为所
    有部门传递消息的费用和。
    幸运的是,如果两个部门可以 直接或间接地相互传递消息(即能按照上述方法
    将信息由 X 传递到 Y,同时能由 Y 传递到 X),我们就可以忽略它们之间的花费。
    由于资金问题(预算都花在粒子对撞机上了),SERN 总部的工程师希望知道,
    达到目标的最小花费是多少。
    【输入格式】多组数据,文件以 20 结尾。
    每组数据第一行,一个整数 N,表示有 N 个包括总部的部门(从 0 开始编号)。
    然后是一个整数 M,表示有 M 条单向通讯线路。
    接下来 M 行,每行三个整数,Xi,Yi,Ci,表示第 i 条线路从 Xi 连向 Yi,花费
    为
    Ci。
    【输出格式】
    每组数据一行,一个整数表示达到目标的最小花费。
    【输入输出样例】
    message.in 
    3 3
    0 1 100 
    1 2 50
    0 2 100
    3 3
    0 1 100
    1 2 50
    2 1 100
    2 2
    0 1 50
    0 1 100
    0 0
    
    message.out
    150
    100
    50
    【样例解释】第一组数据:总部把消息传给分部 1,分部 1 再传给分
    部 2.总费用:
    100+50=150.
    第二组数据:总部把消息传给分部 1,由于分部 1 和分部 2 可以互相传递消
    息,所以分部 1 可以无费用把消息传给 2.总费用:100+0=100.
    第三组数据:总部把消息传给分部 1,最小费用为 50.总费用:50.
    【数据范围】对于 10%的数据,
    保证 M=N-1
    对于另 30%的数据,N ≤ 20 ,M ≤ 20 对于 100%的数据,N ≤ 50000 ,
    M ≤ 10^5 ,Ci ≤ 10^5 ,数据组数 ≤ 5
    数据保证一定可以将信息传递到所有部门。
    题目

    tag:强连通分量,缩点,贪心

    思路:tarjan求强连通分量,缩点后形成DAG的树状结构。乍一看是最小生成树,根据题意,除起点的每个点都有可以到它的边,既然最后都能到达,选最小的那个边就行了。如果用最小生成树,起点可能会“被”连接,用贪心法一开始把minv[st]置0可以避免这种情况。

      1 #include<cstdio>
      2 #include<iostream>
      3 #include<algorithm>
      4 #include<cmath>
      5 #include<stack>
      6 #define maxn 100010
      7 using namespace std;
      8 int cnt,tot,ans,n,m,num,dfn[maxn],low[maxn],vis1[maxn],vis2[maxn],hl[maxn],HL[maxn],jh[maxn],own[maxn],fa[maxn],minv[maxn];
      9 stack<int>S;
     10 struct Edge{
     11     int u,v,w,ne;
     12 }e[maxn<<1],E[maxn<<1];
     13 void init()
     14 {
     15     cnt=tot=ans=num=0;
     16     memset(dfn,0,sizeof(dfn));
     17     memset(low,0,sizeof(low));
     18     memset(vis1,0,sizeof(vis1));
     19     memset(vis2,0,sizeof(vis2));
     20     memset(hl,0,sizeof(hl));
     21     memset(HL,0,sizeof(HL));
     22     memset(e,0,sizeof(e));
     23     memset(E,0,sizeof(E));
     24     memset(jh,0,sizeof(jh));
     25     memset(own,0,sizeof(own));
     26     memset(fa,0,sizeof(fa));
     27     memset(minv,127/3,sizeof(minv));
     28 }
     29 void add(int u,int v,int w)
     30 {
     31     e[++cnt].u=u;
     32     e[cnt].v=v;
     33     e[cnt].w=w;
     34     e[cnt].ne=hl[u];
     35     hl[u]=cnt;
     36 }
     37 int find(int x)
     38 {
     39     return x==fa[x]?x:fa[x]=find(fa[x]);
     40 }
     41 void tarjan(int x)
     42 {
     43     S.push(x);
     44     dfn[x]=low[x]=++tot;
     45     vis1[x]=vis2[x]=1;
     46     for(int i=hl[x];i;i=e[i].ne){
     47         int v=e[i].v;
     48         if(!vis1[v]){
     49             tarjan(v);
     50             low[x]=min(low[x],low[v]);
     51         }
     52         else if(vis2[v]) low[x]=min(low[x],dfn[v]); 
     53     }
     54     if(dfn[x]==low[x]){
     55         num++;
     56         int now=-1;
     57         while(now!=x){
     58             now=S.top();
     59             S.pop();
     60             jh[now]=num;
     61             own[num]++;
     62             vis2[now]=0;
     63         }
     64     }
     65 }
     66 void rebuild()
     67 {
     68     for(int i=0;i<n;++i)
     69         for(int j=hl[i];j;j=e[j].ne){
     70             int v=e[j].v;
     71             if(jh[i]!=jh[v]){
     72                 E[++cnt].u=jh[i];
     73                 E[cnt].v=jh[v];
     74                 E[cnt].w=e[j].w;
     75                 E[cnt].ne=HL[jh[i]];
     76                 HL[jh[i]]=cnt;
     77             }
     78         }
     79 }
     80 bool cmp(Edge x,Edge y)
     81 {
     82     return x.w<y.w;    
     83 }
     84 int main()
     85 {
     86     //freopen("message.in","r",stdin);
     87     //freopen("message.out","w",stdout);
     88     int x,y,w;
     89     while(scanf("%d%d",&n,&m)!=EOF){
     90         init();
     91         if(!n&&!m) break;
     92         for(int i=1;i<=m;++i){
     93             scanf("%d%d%d",&x,&y,&w);
     94             add(x,y,w);
     95         }
     96         for(int i=0;i<n;++i) if(!vis1[i]) tarjan(i);
     97         cnt=tot=0;
     98         rebuild();
     99         minv[jh[0]]=0;
    100         for(int i=1;i<=num;++i)
    101             for(int j=HL[i];j;j=E[j].ne){
    102                 int v=E[j].v;
    103                 minv[v]=min(E[j].w,minv[v]);
    104             }
    105         for(int i=1;i<=num;++i) ans+=minv[i];
    106         printf("%d
    ",ans);
    107     }
    108     return 0;
    109 }

    3.label(label)

    【问题描述】
    Samjia和Peter不同,他喜欢玩树。所以Peter送给他一颗大小为n的树,节
    点编号从1到n。
    Samjia要给树上的每一个节点赋一个[1,m]之间的权值,并使得有边直接相
    连的两个节点的权值之差的绝对值 ≥ k。请你告诉Samjia有多少种不同的赋值
    方案,只用求出答案对10
    9 +7(1000000007)取模得到的结果。
    【输入格式】
    输入文件名为 label.in。
    输入数据的第一行包含一个整数 T,代表测试数据组数。
    接下来是 T 组数据.
    每组数据的第一行包含三个整数 n、m 和 k。
    接下来 n − 1 行,每行包含两个整数 u 和 v, 代表节点 u 和 v 之间有
    一条树边。
    【输出格式】
    输出文件名为 label.out。
    对于每组数据,输出一行,包含一个整数,代表所求的答案。
    【输入输出样例】
    label.in label.out
    3
    2 2 0
    1 2
    3 3 2
    1 3
    1 2
    3 3 1
    1 2
    2 3
    4
    2
    12
    【输入输出样例说明】
    对于第一组样例,满足的方案如图
    图中方括号内的数字([x])代表给节点赋的值。
    【数据规模与约定】
    测试点编号 m ≤ 特殊约定
    1,2 1003,4 100005,6 10^9 第2-n号节点与1号节点直接相连
    7,8 10^9 第i号节点与第i+1号节点直接相连
    9,10 10^9 无
    对于所有数据,T≤10,n≤100,k≤100,m≤10^9
    题目

     tag:树形DP

    思路:对于这道树形DP来说,父节点的取值决定了子节点的取值范围,子节点的方案数通过使用加法原理和乘法原理继承给父节点。但暴力枚举只能拿到20分。我们运用数学归纳,发现对于每个节点X,满足f[x][i]=f[x][m-i+1],也就是说它的取值是对称的。其实,如果可取的长度相等,DP(f)的值也相等。再考虑,如果当m特别大而k很小,就有很长一段区间内每个点的DP值都相等,如下图。

    如果这两个区间同时向外移动,因为对称性,他们的值同步变化。那么,我们有必要把100 * 10^9 个值都存到数组里吗?答案当然是,没有。最多只会有(maxn-1)*maxk个不同的值,也就是说,我们只用保存最多9900(limit)个数,当调用时直接去找每个值存他的地方,把大区间分左(1~limit)、中(limit~m-limit)、右(m-limit~m),在左区间直接调用,让limit储存中区间的所有点的那个相同值,在右区间,找左区间的对称点。

    解决了空间问题,还有更麻烦的时间问题,我们每取一个值,都会生成完全不同的范围,如果每次都进行计算是不是太过麻烦?不过,很容易发现,从1取到limit,它们生成的区间有一定变化规律,大部分是不会变的,左右两端进行微调,右端退出,左端进入(详见注释)。

    我们还需要求出当取值为1时的初始区间,之后才能在上面进行修改。getsum——左中右的处理方式各异,左右每个点值都不同,需要暴力枚举,中区间只求有多少个点再乘值。

    还有个小技巧,k=0的时候直接快速幂m^n输出。最后的答案就是根节点所有DP值的和,我们用现成的getsum可以直接求。

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<algorithm>
     4 #include<cmath>
     5 #include<stack>
     6 #define maxn 10010
     7 #define ll long long
     8 using namespace std;
     9 const int mod = 1e9 + 7;
    10 int ans,k,hl[maxn],fa[maxn],cnt,n,m,T,lim,f[110][maxn];
    11 struct Edge{
    12     int u,v,w,ne;
    13 }e[maxn<<1];
    14 void init()
    15 {
    16     memset(hl,0,sizeof(hl));
    17     memset(fa,0,sizeof(fa));
    18     memset(f,0,sizeof(f));
    19     memset(e,0,sizeof(e));
    20     cnt=ans=0;
    21 }
    22 void add(int u,int v)
    23 {
    24     e[++cnt].u=u;
    25     e[cnt].v=v;
    26     e[cnt].ne=hl[u];
    27     hl[u]=cnt;
    28 }
    29 ll getsum(int x,int st)
    30 {
    31     ll ret=0;
    32     for(int i=st;i<=lim;++i) ret=(ret+f[x][i])%mod;//起点在左区间 计算从起点到lim 
    33     for(int i=m;i>m-lim&&i>lim&&i>=st;--i) ret=(ret+f[x][m-i+1])%mod;//类似上一行 计算右区间 
    34     int l=max(st,lim+1),r=m-lim;//计算中区间范围 
    35     int len=r-l+1;
    36     if(len>0) ret=(ret+1ll*len*f[x][lim]%mod)%mod;
    37     return ret;
    38 }
    39 void dfs(int x)
    40 {
    41     for(int i=hl[x];i;i=e[i].ne){
    42         int v=e[i].v;
    43         if(v==fa[x]) continue;
    44         fa[v]=x;
    45         dfs(v);
    46     }
    47     for(int i=1;i<=lim;++i) f[x][i]=1;
    48     for(int i=hl[x];i;i=e[i].ne){
    49         int v=e[i].v;
    50         if(v==fa[x]) continue;
    51         ll sum=getsum(v,k+1);//初始值 
    52         for(int j=1;j<=lim;++j){
    53             if(j-k>=1) sum=(sum+f[v][j-k])%mod;//左端点增加区间 
    54             f[x][j]=1ll*f[x][j]*sum%mod;//乘法原理 
    55             int bj=j+k;//右端点
    56             if(bj<=m){//右端点还在大范围内
    57                 if(m-bj+1<=lim) bj=m-bj+1;//将右区间定位到左区间
    58                 else if(bj>=lim) bj=lim;//中区间定位到lim点 
    59                 sum=((sum-f[v][bj])%mod+mod)%mod;//右端点退出区间
    60             }
    61         }
    62     }
    63 }
    64 int ksm(int a,int B)
    65 {
    66     int x=a,b=B,ret=1;
    67     while(b){
    68         if(b&1) ret= 1ll*ret*x %mod;
    69         x= 1ll*x*x%mod;
    70         b>>=1;
    71     }
    72     return ret;
    73 }
    74 int main()
    75 {
    76     //freopen("label.in","r",stdin);
    77     //freopen("label.out","w",stdout);
    78     int x,y;
    79     scanf("%d",&T);
    80     while(T--){
    81         init();
    82         scanf("%d%d%d",&n,&m,&k);
    83         for(int i=1;i<n;++i){
    84             scanf("%d%d",&x,&y);
    85             add(x,y);
    86             add(y,x);
    87         }
    88         if(!k){
    89             printf("%d
    ",ksm(m,n));
    90             continue;
    91         }
    92         lim=min(10000,m);
    93         dfs(1);
    94         printf("%d
    ",getsum(1,1));
    95     }
    96     return 0;
    97 }

     ↑系统自带分割线

    芒果君:这次大——————翻车!!!然后订正+解题报告又弄了好久QAQ 无fa可说OTZ

  • 相关阅读:
    sort()的部分用法
    蓝桥杯 算法训练 审美课
    蓝桥杯 基础练习 查找整数
    2018年第九届蓝桥杯【C++省赛B组】【第二题:明码】
    蓝桥杯 特殊回文数
    比较两文本程序
    蓝桥杯 基础练习 十六进制转八进制
    Python | 用Pyinstaller打包发布exe应用
    SpringBoot-04-整合JDBC
    SpringBoot-03-自定义Starter
  • 原文地址:https://www.cnblogs.com/12mango/p/7342286.html
Copyright © 2011-2022 走看看