冲刺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

原文地址:https://www.cnblogs.com/12mango/p/7342286.html