暑假第五次考试 冲刺NOIP模拟赛
Posted 这里有十二吨芒果
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了暑假第五次考试 冲刺NOIP模拟赛相关的知识,希望对你有一定的参考价值。
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 总部的工程师希望知道, 达到目标的最小花费是多少。 【输入格式】多组数据,文件以 2 个 0 结尾。 每组数据第一行,一个整数 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\\n",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 100 无 3,4 10000 无 5,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\\n",ksm(m,n)); 90 continue; 91 } 92 lim=min(10000,m); 93 dfs(1); 94 printf("%d\\n",getsum(1,1)); 95 } 96 return 0; 97 }
↑系统自带分割线
芒果君:这次大——————翻车!!!然后订正+解题报告又弄了好久QAQ 无fa可说OTZ
以上是关于暑假第五次考试 冲刺NOIP模拟赛的主要内容,如果未能解决你的问题,请参考以下文章
暑假第四次考试 冲刺Noip模拟赛4 解题报告——五十岚芒果酱
暑假第二次考试 冲刺Noip2017模拟赛2 解题报告——五十岚芒果酱