noi online round 1(入门组)
发现还是不简单。。。想要得满分的话还是很困难的 。。。但是看了一下第一题不是很难,第二题通过动态规划,还是能得到80分,如果没有分析好的话搜索好像也能得30分还是40分。。第三题做不来就只考虑k=0的情况,也有20分,所以运气好的话能得到200分,不好100分吧~
但是如果真让我来做,,,,算了吧,还是太菜了。。o(╥﹏╥)o
第一题:文具订购
这个题目真的还是需要自己在纸上去推算,就把小于14的情况能怎么去买计算出来,有特殊的情况就是不能买,如果总的钱没有14大,就直接不行;但是大于14的话,就拆掉一套来补上,比如能买3套,买完后剩1块钱,有要求全部花完,所以就拆掉一套,现在是2套剩15元,15就自己去推怎么买最合适,就直接计算出来了。就这样把剩余的钱怎样买的情况算出来就可以了
不过实在没有什么思路的话,其实你就算那几种特殊的情况也能得很多分了~
#include <bits/stdc++.h> using namespace std; int n; //骗分很好骗 int rb[14]={0,0,1,0,1,1,0,1,2,0,1,2,0,1}; int rc[14]={0,5,4,1,0,5,2,1,0,3,2,1,4,3}; int a,b,c; int main() { cin>>n; if(n==1||n==2||n==5){ printf("-1\n");return 0; } int cnt=n/14; n=n%14; if(n==1||n==2||n==5) cnt--; a=cnt;b=cnt+rb[n];c=cnt+rc[n]; printf("%d %d %d\n",a,b,c); return 0; }
第二题:跑步
做法有点多,但是看了一下,可能能得全分的就只有两种做法;
第一种:用搜索的话分是得得最少的,如果去用动态规划的话分肯定是得不全,但是加上空间优化也能得80分应该,所以在这里就有一个很巧妙的做法:直接看代码吧
#include <bits/stdc++.h> using namespace std; const int N=100005,inf=0x3f3f3f;//应该可以得80分 #define LL long long int /*因为反正就是求n的组合有多少 所以除开五边形数定理那个做法以外,还有一种方法应该会好理解一点 就是同样是用DP,类似于完全背包问题, 就先求出小于根号n的情况有多少 大于根号n的情况有多少 然后再根据乘法原理来进行合并 设f(n,k)为n拆成k个正整数部分有f(n,k)种方案(k由小到大排列) f(n,k)=f(n-1,k-1)+f(n-k,k) */ int ans=0,d[N],f[N][330]; int n,m,p; int main(){ cin>>n>>p; m=sqrt(n)+1; d[0]=f[0][0]=1; for(int i=1;i<=m;i++) for(int j=i;j<=n;j++) d[j]=(d[j]+d[j-i])%p;//小于根n m++; for(int i=m;i<=n;i++){ f[i][1]=1; for(int j=2;j<=m;j++) f[i][j]=(f[i-j][j]+f[i-m][j-1])%p; } for(int i=0;i<=n;i++){ for(int j=0;j<=m;j++) ans=(ans+1ll*d[i]*f[n-i][j])%p; } printf("%d",ans); }
第二种:就是五边形数定理了,这个没看过的肯定想不出来,。。。好吧像我太菜了也是似懂非懂,下来去看了一下母函数的相关知识,还有就是五边形数定理,就当成一个定理记住吧,反正就记住那个值是怎样在变化的就行了。。(个人观点)
#include <bits/stdc++.h> using namespace std; const int N=100010,inf=0x3f3f3f; int n,p,f[N],g[N]; int main() { cin>>n>>p; int m=0; for(int k=1;g[m]<=n;k++){ g[++m]=(3*k*k-k)/2; g[++m]=(3*k*k+k)/2; } f[0]=1; for(int i=1;i<=n;i++) for(int j=1,t=1;g[j]<=i;j++) { f[i]=(f[i]+f[i-g[j]]*t)%p; if(j%2==0) t*=-1; } cout<<(f[n]+p)%p<<endl; return 0; } /*五边形数定理:(1-x)(1-x^2)(1-x^3)...=1-x-x^2+x^5+x^7-x^12-x^15+x^22+x^26... 指数变化规律 (3k^2-k)/2 (3k^2+k)/2 pn=pn-1+pn-2-pn-5-pn-7+.... 构造函数: f(x)=1+p1x^1+p2x^2+....+pnx^n+.....=(1+x+x^2+x^3+..)(1+x^2+x^4+..)(1+x^3+x^6...) =1/(1-x)(1-x^2)(1-x^3)... f(x)*(1-x)(1-x^2)...=1 f(x)*(1-x-x^2+x^5+x^7-x^12-x^15...)=1; (1+p1x^1+p2x^2)*(1-x-x^2+x^5+x^7-x^12-x^15)=1; xn的系数:pn-pn-1-pn-2+pn-5+pn-7...=0 pn=... */
第三题:魔法;
好吧由于没有学过线性代数,,学习一下矩阵的乘法吧,虽然有讲过但是记不太清楚了,首先能够进行相乘的条件就是矩阵A的行数列数等于矩阵B的列数,
例如[1 2 3]
[1 2 3
1 2 3
1 2 3 ]
那么就等于[1*1+2*1+3*1 1*2+2*2+3*2 1*3+2*3+3*3]=[6 12 18]
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) c[i][j]+=a[i][k]*b[k][j];
下一个知识:快速幂(相关代码)
int quickPow(int a,int b,int n) { if(b==1) return a; if(b%2==0)//偶数的情况 { int t=quickPow(a,b/2,n); return t*t%n; } else {//奇数的情况 int t=quickPow(a,b/2,n); t=t*t%n;t=t*a%n; return t; } } int quickPow(int a,int b,int n) { int res=1; while(b){ if(b%2==1) res=res*a%n; a=a*a%n; b=b/2; } return res; }
下一个知识:矩阵快速幂
由矩阵乘法的定义可知,如果a*a合法,那么a的行等于a的列,所以可以快速幂的矩阵必须是方阵(行和列相等)
矩阵:
struct node{ int mat[15][15];//定义矩阵 }x,y;
矩阵相乘:
node mul(node x,node y)//矩阵乘法 { node tmp; for(int i=0;i<len;i++) for(int j=0;j<len;j++) { tmp.mat[i][j]=0; for(int k=0;k<len;k++) tmp.mat[i][j]+=(x.max[i][k]*y.mat[k][j])%mod; tmp.mat[i][j]=tmp.mat[i][j]%mod; } }
矩阵快速幂:
node matpow(node x,node y,int num)//矩阵快速幂 { while(num){ if(num&1) y=mul(y,x); x=mul(X,x); num=num>>1; } return y; }
还有一种写法:
node matpow(node x,int n) { node y; for(int i=0;i<len;i++) y.mat[i][i]=1;//好像是规定的对角线为1 while(n)//后面就差不多了 { if(n&1) y=mul(y,x); x=mul(x,x); n=n>>1; } }
so.......思路有了,做法感觉我迷迷糊糊的写了好多,但其实都是一样的咳咳
开始没有环的情况过不了,原因是因为mul函数里面的最大值复制与结构体中重复了,如果知道重载函数就比较好看这个代码,不知道的话就把我注释掉的结合中而看吧。。
注释的内容结合起来是有三种不一样的写法,但其实思路都是一样的。。。代码可能有点长,因为我下面也自己写了很多
#include <bits/stdc++.h> using namespace std; const int N=3050,M=5005,inf=0x3f3f3f3f3f3f3f;//也可以骗k=0的情况 long long d[105][105]; struct edge{ int x,y,w; }e[N]; int n,m,K; struct node{ long long mat[105][105]; node(int x=0x3f){memset(mat,x,sizeof(mat));} /* node operator*(const node&b)const { node ans; for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ans.mat[i][j]=min(ans.mat[i][j],mat[i][k]+b.mat[k][j]); return ans; } */ }a; node mul(node q,node p) { node c; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) c.mat[i][j]=min(c.mat[i][j],p.mat[i][k]+q.mat[k][j]); return c; } node matpow(node p,int q) { if(q==1) return p; node res=matpow(p,q>>1); if(q%2==1) return mul(mul(res,p),res); else return mul(res,res); } /* node matpow(node x,int y) { node ans; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ans.mat[i][j]=d[i][j]; while(y) { if(y&1) ans=mul(ans,x);//ans=ans*x; x=mul(x,x); y>>=1; } return ans; } */ int main() { memset(d,0x3f,sizeof(d)); cin>>n>>m>>K; for(int i=1;i<=n;i++) d[i][i]=0; for(int i=1;i<=m;i++){ cin>>e[i].x>>e[i].y>>e[i].w; d[e[i].x][e[i].y]=e[i].w;//有向图 } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]);//Floyd求出任意两点之间的最短距离 for(int k=1;k<=m;k++) { int u=e[k].x,v=e[k].y,w=e[k].w;//枚举要 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a.mat[i][j]=min(a.mat[i][j],min(d[i][j],d[i][u]+d[v][j]-w));//施展一次魔法的最佳情况(但有可能施展也有能不施展) } if(K==0) cout<<d[1][n]<<endl;//不使用魔法的情况 else cout<<matpow(a,K).mat[1][n]<<endl; } /*预备知识 矩阵相乘 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) c[i][j]+=a[i][k]*b[k][j]; 快速幂 int quickPow(int a,int b,int n) { if(b==1) return a; if(b%2==0)//偶数的情况 { int t=quickPow(a,b/2,n); return t*t%n; } else {//奇数的情况 int t=quickPow(a,b/2,n); t=t*t%n;t=t*a%n; return t; } } int quickPow(int a,int b,int n) { int res=1; while(b){ if(b%2==1) res=res*a%n; a=a*a%n; b=b/2; } return res; } 矩阵快速幂 struct node{ int mat[15][15];//定义矩阵 }x,y; node mul(node x,node y)//矩阵乘法 { node tmp; for(int i=0;i<len;i++) for(int j=0;j<len;j++) { tmp.mat[i][j]=0; for(int k=0;k<len;k++) tmp.mat[i][j]+=(x.max[i][k]*y.mat[k][j])%mod; tmp.mat[i][j]=tmp.mat[i][j]%mod; } } node matpow(node x,int n) { node y; for(int i=0;i<len;i++) y.mat[i][i]=1;//好像是规定的对角线为1 while(n)//后面就差不多了 { if(n&1) y=mul(y,x); x=mul(x,x); n=n>>1; } } node matpow(node x,node y,int num)//矩阵快速幂 { while(num){ if(num&1) y=mul(y,x); x=mul(X,x); num=num>>1; } return y; } /* 状态:f(k,i,j)表示从i到j最多用了k次魔法的所有方案的最小值 状态转移: f(k,i,j)=min(f(k,i,j),f(k-1,i,t)+f(1,t,j)); 有点类似矩阵乘法 0次:Floyd 1次:枚举 右边:a->b 所以:如果把每个 fk 看成一个矩阵,那么一次转移就是 f的一次自乘,把矩阵乘法中的加号换成 min。 */
好了,终于把第一次的写完了~~~
今天又写了一种:(思路更加清晰啦)
#include <bits/stdc++.h> #define N 105 #define M 3505 #define inf 0x3f3f3f3f3f3f3f using namespace std; int u[M],v[M],t[M],n,m,kk; long long floyd[N][N]; struct node{ long long mat[N][N]; }aa; node mul(node x,node y){ node z; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) z.mat[i][j]=inf; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) z.mat[i][j]=min(x.mat[i][k]+y.mat[k][j],z.mat[i][j]); return z; } node quickpow(node a,int b){ if(b==1)return a; node res=quickpow(a,b>>1); if(b%2==1) return mul(mul(res,a),res); else return mul(res,res); } int main(){ scanf("%d %d %d",&n,&m,&kk); memset(floyd,0x3f,sizeof(floyd)); for(int i=1;i<=n;i++) floyd[i][i]=0; for(int i=1;i<=m;i++){ scanf("%d %d %d",&u[i],&v[i],&t[i]); floyd[u[i]][v[i]]=t[i]; } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) floyd[i][j]=min(floyd[i][k]+floyd[k][j],floyd[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) aa.mat[i][j]=inf; for(int k=1;k<=m;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) aa.mat[i][j]=min(min(floyd[i][u[k]]+floyd[v[k]][j]-t[k],floyd[i][j]),aa.mat[i][j]); if(kk==0) printf("%lld",floyd[1][n]); else cout<<quickpow(aa,kk).mat[1][n]<<endl; return 0; }