2017.4.15考试总结
2017-04-15 16:30
183 查看
本次考试的三道题总体难度并不是太高,不及NOIP的难度,但是结果成绩爆0,还是因为自己的基础内容不扎实(高精度),一些类型的题不熟练(超简单的树形DP),还有考场上没有合理分配时间,静下心去分析一道题,只是粗略的一看,感觉比较难就放弃了。
第一题:求a^b-b^a的值(0<=a,b<=100)
这道题一看数据范围肯定是高精度,如果数据再大一点,就要用到快速幂。据一位张姓学霸讲,用FFT+快速幂可以将复杂度降到 nlog(n),但是对于FFT这种NOI算法我并不与会,在这里就不解释了。
主要是用两次高精度乘法分别求a^b和b^a,然后再用一次高精度减法求出答案。
当然这道题亦可以用高精度压位,相当于万进制,就比较复杂,但更加节省空间(以下为高精度压位的代码)
第二题:裸的树形DP,像我这种蒟蒻都可以一眼看穿,满心欢喜的敲了一个半小时,结果建的单向边,全错。
题意:一棵树,每个节点有一个权值,父亲节点和儿子节点不能同时取,求如何取才有最大值,输出这个最大值。
f[][0]表示不取当前这个点的最大值,f[][1]表示取当前这个点的最大值
第三题:由于前面浪费了太多时间,看题时就剩下了半个小时,心里异常浮躁,根本没有仔细思考,其实这是一道比较简单的结论题,当你看到点和边都有权值时,就要想办法把点的值转移到边上,这时候就需要分析一下题意了。
题意:一个图,从任意一个点出发,遍历每一个点之后返回,每个边和点都有一个代价,输出最小代价。通过一定的举例分析流程,我们不难发现每走到一个点,代价之和为边的代价*2+边的起始点和终点的代价,所以我们在读入数据时直接将每条边的边权e[i].w=e[i].w*2+w[e[i].str]+w[e[i].to],但是我们注意到由于最后会再一次回到起点,所以答案应该为图的最小生成树的边权和加上起点的权值,所以我们只需要用克鲁斯卡尔算法求图的最小生成树。因为当图固定,边权和也随之固定,所以我们只需要在一开始遍历一次,找到权值最小的点作为起点即可
后记:
1、考试又一次炸了,归根结底时知识的不完善和算法理解不透彻
2、虽然0分,但还是要相信自己的能力,毕竟第二题想出了正解
3、最后引用一句话“自己选的路,跪着也要走完”
第一题:求a^b-b^a的值(0<=a,b<=100)
这道题一看数据范围肯定是高精度,如果数据再大一点,就要用到快速幂。据一位张姓学霸讲,用FFT+快速幂可以将复杂度降到 nlog(n),但是对于FFT这种NOI算法我并不与会,在这里就不解释了。
主要是用两次高精度乘法分别求a^b和b^a,然后再用一次高精度减法求出答案。
#include<iostream> #include<algorithm> #include<cstdlib> #include<cstdio> #include<string> #include<cstring> #include<ctime> #include<cmath> using namespace std; int a,b; int x[220],y[220]; bool calc() { for (int i=219;i>=0;i--)//100^100也只有219位 { if (x[i]==y[i]) continue; return x[i]<y[i]; } return false; } int main() { scanf("%d%d",&a,&b); x[0]=y[0]=1; for (int i=1;i<=b;i++)//求a^b { int r=0; for (int j=0;j<220;j++) { x[j]=x[j]*a+r; r=x[j]/10; x[j]%=10; } } for (int i=1;i<=a;i++)//求b^a { int r=0; for (int j=0;j<220;j++) { y[j]=y[j]*b+r; r=y[j]/10; y[j]%=10; } } if (calc())//判断是否为负 { for (int i=0;i<220;i++) swap(x[i],y[i]); printf("-"); } int r=0; for (int i=0;i<220;i++)//高精度减法 { x[i]=x[i]-y[i]+r+10; r=x[i]/10-1; x[i]%=10; } for (r=219;r;r--)//去除空位 if (x[r]) break; for (int i=r;i>=0;i--)//输出答案 printf("%d",x[i]); return 0; }
当然这道题亦可以用高精度压位,相当于万进制,就比较复杂,但更加节省空间(以下为高精度压位的代码)
#include <cstdio> #include <algorithm> using namespace std; const int maxn=2600; struct BigNumber { int n,a[maxn]; BigNumber(int x=1){a[n=1]=x;} void operator*=(int x) { for(int i=1;i<=n;++i)a[i]*=x; for(int i=1;i<=n;++i)if(a[i]>=10000) { a[i+1]+=a[i]/10000; a[i]%=10000; } while(a[n+1]>0) { ++n; if(a >=10000) { a[n+1]+=a /10000; a %=10000; } } } void print() { printf("%d",a ); for(int i=n-1;i;--i)printf("%04d",a[i]);puts(""); } }; BigNumber operator-(BigNumber a,const BigNumber &b) { BigNumber c(0); for(int i=1;i<=a.n;++i) { c.a[i]=a.a[i]-b.a[i]; if(c.a[i]<0)--a.a[i+1],c.a[i]+=10000; } c.n=1; for(int i=a.n;i;--i)if(c.a[i]>0){c.n=i;break;} return c; } bool operator<=(cons 4000 t BigNumber &a,const BigNumber &b) { if(a.n>b.n)return 0; if(a.n<b.n)return 1; for(int i=a.n;i;--i)if(a.a[i]<b.a[i])return 1;else if(a.a[i]>b.a[i])return 0; return 1; } int main() { //freopen("calc.in","r",stdin); //freopen("calc.out","w",stdout); int a,b; scanf("%d%d",&a,&b); static BigNumber M,N; for(int i=1;i<=b;++i)M*=a; for(int i=1;i<=a;++i)N*=b; if(N<=M)(M-N).print(); else { printf("-"); (N-M).print(); } return 0; }
第二题:裸的树形DP,像我这种蒟蒻都可以一眼看穿,满心欢喜的敲了一个半小时,结果建的单向边,全错。
题意:一棵树,每个节点有一个权值,父亲节点和儿子节点不能同时取,求如何取才有最大值,输出这个最大值。
f[][0]表示不取当前这个点的最大值,f[][1]表示取当前这个点的最大值
#include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<iomanip> #include<iostream> #include<cctype> #include<algorithm> using namespace std; long long n,tot,f[100005][2],next[100005*2]; lnog long first[100005],w[100005],to[100005*2]; bool v[100005]; //--------------------- inline void Add(int u,int v) { next[++tot]=first[u]; first[u]=tot; to[tot]=v; } //--------------------- inline void dfs(int x)//先从根到叶子节点初始化,然后再从叶子节点递归回来更新每个点的值 { int i; v[x]=true; f[x][0]=0,f[x][1]=w[x]; for(int k=first[x];k;k=next[k]) if(!v[i=to[k]]) { dfs(i); f[x][0]=max(f[i][0],f[i][1]);//子节点可以去也可以不去,比如当它的权值为负肯定不取 f[x][1]+=f[i][0]; } } //--------------------- int main() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); memset(v,0,sizeof(v)); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%I64d",&w[i]); for(int i=1,u,v;i<n;i++){ scanf("%d%d",&u,&v); Add(u,v); Add(v,u); //一定要建双向边否则会出现一个节点两个父亲的情况就不符合树的定义 } dfs(1); cout<<max(f[1][0],f[1][1]); return 0; }
第三题:由于前面浪费了太多时间,看题时就剩下了半个小时,心里异常浮躁,根本没有仔细思考,其实这是一道比较简单的结论题,当你看到点和边都有权值时,就要想办法把点的值转移到边上,这时候就需要分析一下题意了。
题意:一个图,从任意一个点出发,遍历每一个点之后返回,每个边和点都有一个代价,输出最小代价。通过一定的举例分析流程,我们不难发现每走到一个点,代价之和为边的代价*2+边的起始点和终点的代价,所以我们在读入数据时直接将每条边的边权e[i].w=e[i].w*2+w[e[i].str]+w[e[i].to],但是我们注意到由于最后会再一次回到起点,所以答案应该为图的最小生成树的边权和加上起点的权值,所以我们只需要用克鲁斯卡尔算法求图的最小生成树。因为当图固定,边权和也随之固定,所以我们只需要在一开始遍历一次,找到权值最小的点作为起点即可
#include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<algorithm> #include<iomanip> #include<iostream> #include<cctype> #include<cstring> #include<string> #include<set> #include<queue> using namespace std; const int INF=1e9+1; const int N=100005; int n,m,ans,tot,father ,w ; //--------------------- struct node { int x,y,w; }e ; //--------------------- bool cmp(const node&a,const node&b) { return a.w<b.w; } //--------------------- inline int find(int x) { if(x==father[x]) return x; father[x]=find(father[x]); return father[x]; } //--------------------- int main() { freopen("cheer.in","r",stdin); freopen("cheer.out","w",stdout); int ans=INF; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) father[i]=i,scanf("%d",&w[i]),ans=min(ans,w[i]);//找到权值最小的作为起点 for(int i=1;i<=m;i++){ scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w); e[i].w=e[i].w*2+w[e[i].x]+w[e[i].y];//将边权×2再加上两端的权值 } sort(e+1,e+1+m,cmp);//最小生成树算法 for(int i=1;i<=m;i++){ if(tot==n-1) break; if(find(e[i].x)!=find(e[i].y)){ tot++; father[find(e[i].x)]=find(e[i].y); ans+=e[i].w; } } cout<<ans<<endl; }
后记:
1、考试又一次炸了,归根结底时知识的不完善和算法理解不透彻
2、虽然0分,但还是要相信自己的能力,毕竟第二题想出了正解
3、最后引用一句话“自己选的路,跪着也要走完”