第四次练习赛解题报告及标程
2014-04-19 01:37
225 查看
第四次练习赛本来想有点创新,结果还是很不幸地做得不够好。
以下分别放出两个版本,先是dfs的。
然后是bfs的。
这道题模拟了单调栈的基本操作,同时在hint里给出了单调栈的入门练习题(POJ2559)。因为介绍它的文章和题解一大堆,这里不多解释。
首先是能拿到0.5分(1≤m≤30,0≤n≤m)的做法,最普通的递归。每次查询复杂度是指数级的。
其次是能拿到0.8分(1≤m≤10^3,0≤n≤m)的做法,即把递归的中间过程用数组记录下来(记忆化搜索),或者干脆直接dp。复杂度上,预处理O(m×n),查询O(1)。
接下来是能拿到1分满分(1≤m≤5×10^5,0≤n≤m)的做法。
李晨豪的做法。他首先通过Euler筛法线性处理出素数表,然后将每次询问进行质因数分解,快速幂再求和就可以了;质因数分解的方法是,对这个组合数的三个阶乘,分别筛出每个阶乘的质因数个数。预处理复杂度O(m),查询复杂度不太好分析,上界大概是O(m)(也可能是O(mlogm),我不确定);基本上是因为数据组数小(10组左右)水过去的。
户建坤的做法。首先算出n!求模的结果,以及(m-n+1)乘到m并求模的结果,然后因为求模的存在,不能直接用后者除以前者,所以用扩展gcd拿出前者关于模的乘法逆元,然后两者相乘即可。查询复杂度O(n),其中阶乘是O(n),扩展gcd是O(logn);因为数据组数(查询次数)小,这是目前代码中跑得最快的。
何玥的做法。Lucas定理,专门用于解决组合数求模问题,虽然我知道这个定理但是不会用也不打算用……证明比较复杂,可以在网上搜一下。复杂度同样不好分析,大概每次查询O(mlogm)的样子,我不是很清楚。
最后是我的做法……线性预处理出阶乘以及阶乘关于模的逆元,然后相乘即可;其中线性预处理逆元感谢昂神指点(我以前的板子是利用费马小定理做快速幂,复杂度是O(mlogm)的)。预处理复杂度O(m),查询复杂度O(1),是满分做法中理论复杂度最优且代码最短的……(身为ACMer必然是各种板子都写过了Orz)
以上就是全部题解了。如果做练习赛的人能够再多一些就好了……(大概我又想多了?)
A. AString.h(I)
判断并输出所有是回文串的字符串,唯一要注意的仅仅是分割字符串。数据量其实很小。//by trashLHC #include<iostream> #include<cstdio> #include<cstdlib> #include<fstream> #define MAX 101 using namespace std; bool isSymmetric(string s){ cout<<s<<endl; for(int i=0;i<s.length()/2;i++) if(s[i]!=s[s.length()-i-1]&&s[i]!=s[s.length()-i-1]-32&&s[i]!=s[s.length()-i-1]+32) return false; return true; } string solve(string s){ int place[MAX],top=0; place[top++]=-1; for(int i=0;i<s.length();i++) if(s[i]==' ') place[top++]=i; place[top++]=s.length(); for(int i=top-1;i>0;i--){ if(!isSymmetric(s.substr(place[i-1]+1,place[i]-place[i-1]-1))){ s.erase(place[i-1]+1,place[i]-place[i-1]); } } return s; } int main(){ //ifstream infile("in.txt",ios::in); //ofstream outfile("out.txt",ios::out); int n; infile>>n; infile.get(); while(n--){ string s; getline(infile,s); outfile<<solve(s)<<endl; } }
//by wjfwzzc #include<cstdio> #include<cstring> #include<cctype> using namespace std; char str[105]; bool isPalin(char *s) { int l=strlen(s); for(int i=0; i<(l>>1); ++i) if(tolower(s[i])!=tolower(s[l-i-1])) return false; return true; } int main() { int n; scanf("%d",&n); getchar(); while(n--) { gets(str); char *sub=strtok(str," "); while(sub) { if(isPalin(sub)) printf("%s ",sub); sub=strtok(NULL," "); } putchar('\n'); } }
B. AString.h(II)
这道题更简单了,匹配字符串并删除,数据量同样很小。用string类自带函数就可以轻易解决。#include<iostream> #include<string> using namespace std; int main() { ios::sync_with_stdio(false); string s,t; while(cin>>s) { cin.get(); getline(cin,t); int k=t.find(s); for(int i=t.find(s); i!=t.npos; i=t.find(s)) t.erase(i,s.size()); cout<<t<<endl; } }
C. MH370的黑匣子
就算它题干变得再花,也不过就是个迷宫。只判断是否存在路径,不要求路径最短,所以dfs即可;bfs也可以不过稍微难写一点点。以下分别放出两个版本,先是dfs的。
#include<cstdio> #include<cstring> using namespace std; const int dx[]= {-1,0,1,0},dy[]= {0,1,0,-1}; char g[15][15]; bool dfs(int x,int y) { g[x][y]='#'; for(int i=0; i<4; ++i) { int tx=x+dx[i],ty=y+dy[i]; if(g[tx][ty]=='B'||(g[tx][ty]=='*'&&dfs(tx,ty))) return true; } return false; } int main() { int n,m,sx,sy; while(~scanf("%d%d",&n,&m)) { memset(g,'#',sizeof(g)); for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j) { scanf(" %c",&g[i][j]); if(g[i][j]=='S') { sx=i; sy=j; } } puts(dfs(sx,sy)?"Yes":"No"); } }
然后是bfs的。
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int dx[]= {-1,0,1,0},dy[]= {0,1,0,-1}; char g[15][15]; int sx,sy; bool bfs() { queue<pair<int,int> > q; q.push(make_pair(sx,sy)); while(!q.empty()) { pair<int,int> tmp=q.front(); q.pop(); for(int i=0; i<4; ++i) { int tx=tmp.first+dx[i],ty=tmp.second+dy[i]; if(g[tx][ty]=='B') return true; else if(g[tx][ty]=='*') { q.push(make_pair(tx,ty)); g[tx][ty]='#'; } } } return false; } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { memset(g,'#',sizeof(g)); for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j) { scanf(" %c",&g[i][j]); if(g[i][j]=='S') { sx=i; sy=j; } } puts(bfs()?"Yes":"No"); } }
D. 传话游戏
那些个吐槽题干描述不清晰的童鞋们,这道题可是微软2013编程之美资格赛第1题原题……照例注意字符串的分割,然后模拟一下替换过程就好了。标程用了map来记录替换字典,也可以写个pair或者struct。#include<cstdio> #include<cstring> #include<vector> #include<string> #include<map> using namespace std; int main() { int t,n,m; char a[22],b[22],str[105]; scanf("%d",&t); for(int cas=1; cas<=t; ++cas) { scanf("%d%d",&n,&m); map<string,string> dic; vector<string> stc; vector<string>::iterator it; while(m--) { scanf("%s%s",a,b); dic[a]=b; } getchar(); gets(str); char *sub=strtok(str," "); while(sub) { stc.push_back(sub); sub=strtok(NULL," "); } while(--n) for(it=stc.begin(); it!=stc.end(); ++it) if(dic[*it]!="") *it=dic[*it]; printf("Case #%d:\n",cas); for(it=stc.begin(); it!=stc.end(); ++it) printf("%s ",(*it).c_str()); putchar('\n'); } }
E. Monotonic Stack
接下来的三道题都属于开阔视野的类型。建议想要尝试ACM竞赛的童鞋好好研究一下。这道题模拟了单调栈的基本操作,同时在hint里给出了单调栈的入门练习题(POJ2559)。因为介绍它的文章和题解一大堆,这里不多解释。
#include<cstdio> #include<cstdlib> #include<cstring> using namespace std; const int MAXN=100005; int MonotonicStack[MAXN],top; int main() { int m,e; char op[10]; while(~scanf("%d",&m)) { top=-1; while(m--) { scanf("%s",op); switch(op[1]) { case 'U': scanf("%d",&e); while(top!=-1&&MonotonicStack[top]>=e) --top; MonotonicStack[++top]=e; break; case 'O': if(top!=-1) --top; break; case 'E': if(top==-1) puts("EMPTY"); else printf("%d\n",MonotonicStack[top]); break; } } } }
#include<cstdio> #include<stack> using namespace std; stack<int> MonotonicStack; int main() { int m,e; char op[10]; while(~scanf("%d",&m)) { while(m--) { scanf("%s",op); switch(op[1]) { case 'U': scanf("%d",&e); while(!MonotonicStack.empty()&&MonotonicStack.top()>=e) MonotonicStack.pop(); MonotonicStack.push(e); break; case 'O': if(!MonotonicStack.empty()) MonotonicStack.pop(); break; case 'E': if(MonotonicStack.empty()) puts("EMPTY"); else printf("%d\n",MonotonicStack.top()); break; } } while(!MonotonicStack.empty()) MonotonicStack.pop(); } }
F. Monotonic Queue
这道题模拟了单调队列的基本操作,也给出了单调队列的入门练习题(POJ2823),和上面一样两道题都是很有趣的问题,而且相关题解众多,不多阐述。#include<cstdio> #include<cstdlib> #include<cstring> using namespace std; const int MAXN=1005; int MonotonicQueue[MAXN],front,size; int main() { int n,m,e; char op[10]; while(~scanf("%d%d",&n,&m)) { front=size=0; while(m--) { scanf("%s",op); switch(op[1]) { case 'U': scanf("%d",&e); while(size>0&&MonotonicQueue[(front+size-1)%n]>=e) --size; if(size==n) { front=(front+1)%n; --size; } MonotonicQueue[(front+(size++))%n]=e; break; case 'O': if(size>0) { front=(front+1)%n; --size; } break; case 'E': if(size==0) puts("EMPTY"); else printf("%d\n",MonotonicQueue[front]); break; } } } }
#include<cstdio> #include<deque> using namespace std; deque<int> MonotonicQueue; int main() { int n,m,e; char op[10]; while(~scanf("%d%d",&n,&m)) { while(m--) { scanf("%s",op); switch(op[1]) { case 'U': scanf("%d",&e); while(!MonotonicQueue.empty()&&MonotonicQueue.back()>=e) MonotonicQueue.pop_back(); if(MonotonicQueue.size()==n) MonotonicQueue.pop_front(); MonotonicQueue.push_back(e); break; case 'O': if(!MonotonicQueue.empty()) MonotonicQueue.pop_front(); break; case 'E': if(MonotonicQueue.empty()) puts("EMPTY"); else printf("%d\n",MonotonicQueue.front()); break; } } MonotonicQueue.clear(); } }
G. Jeffrey的组合数
这道题就是最普通的组合数求模。我非常欣喜地看到童鞋们给出了相当多种不同的做法;虽然因为数据组数比较小,以至于有些以为会TLE的代码也过了,但依然为其中的想法感到很高兴。限于时间原因,这里给出的一些做法不能涵盖所有做法。首先是能拿到0.5分(1≤m≤30,0≤n≤m)的做法,最普通的递归。每次查询复杂度是指数级的。
#include<cstdio> using namespace std; const int INF=1000000007; long long C(int m,int n) { if(n==0||m==n) return 1; return (C(m-1,n)+C(m-1,n-1))%INF; } int main() { int m,n; while(~scanf("%d%d",&m,&n)) printf("%lld\n",C(m,n)); }
其次是能拿到0.8分(1≤m≤10^3,0≤n≤m)的做法,即把递归的中间过程用数组记录下来(记忆化搜索),或者干脆直接dp。复杂度上,预处理O(m×n),查询O(1)。
#include<cstdio> #include<algorithm> using namespace std; const int MAXN=1005; const int INF=1000000007; long long C[MAXN][MAXN]; void init() { C[0][0]=1; for(int i=1; i<MAXN; ++i) { C[i][0]=1; for(int j=1; j<=min(i,MAXN-1); ++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%INF; } } int main() { init(); int m,n; while(~scanf("%d%d",&m,&n)) printf("%lld\n",C[m] ); }
接下来是能拿到1分满分(1≤m≤5×10^5,0≤n≤m)的做法。
李晨豪的做法。他首先通过Euler筛法线性处理出素数表,然后将每次询问进行质因数分解,快速幂再求和就可以了;质因数分解的方法是,对这个组合数的三个阶乘,分别筛出每个阶乘的质因数个数。预处理复杂度O(m),查询复杂度不太好分析,上界大概是O(m)(也可能是O(mlogm),我不确定);基本上是因为数据组数小(10组左右)水过去的。
//by 李晨豪 #include <cstdio> #include <iostream> using namespace std; const long long MAXN = 1e9 + 7; int n,m; long long ans = 0; int numx = 0; long long f[60100]; bool prime[501000]; void pri() { for (int i = 2; i <= 501000; i++) { if (prime[i] == 0) f[++numx] = i; for (int j = 1; j <= numx && i * f[j] <= 500000; j++) { prime[i * f[j]] = 1; if (i % f[j] == 0) break; } } } void calc(long long & x, long long a,int b) { while(b) { if(b&1) { x = (x * a) % MAXN; } a = (a * a) % MAXN; b >>= 1; } } int ca(long long x,long long p) { long long aans = 0; long long rec = p; while(x>=rec) { aans += x/rec; rec *= p; } return aans; } int main() { pri(); while (~scanf("%d %d",&n,&m)) { ans = 1; for (int i = 1; i <= numx; i++) { int tmp = ca((long long)n,f[i]) - ca((long long)m,f[i])-ca((long long)(n-m),f[i]); calc(ans,f[i],tmp); } printf("%lld\n",ans); } }
户建坤的做法。首先算出n!求模的结果,以及(m-n+1)乘到m并求模的结果,然后因为求模的存在,不能直接用后者除以前者,所以用扩展gcd拿出前者关于模的乘法逆元,然后两者相乘即可。查询复杂度O(n),其中阶乘是O(n),扩展gcd是O(logn);因为数据组数(查询次数)小,这是目前代码中跑得最快的。
//by 户建坤 #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <ctype.h> #include <cstdlib> #include <algorithm> using namespace std; const long long maxn = 1000000007; long long extgcd(long long a,long long b,long long&x,long long&y) { long long d,t; if(b==0) { x = 1; y = 0; return a; } d = extgcd(b,a%b,x,y); t = x; x = y; y = t-a/b*y; return d; } long long get_mod(long long a, long long b) { long long ans = 1; for(long long x = a;x <= b;x++) { ans = (x*ans)%maxn; } return ans; } long long m, n; long long mm, nn; long long x, y; int main() { while(scanf("%lld %lld", &m, &n) != EOF) { mm = get_mod(m - n + 1, m); nn = get_mod(1, n); extgcd(nn,maxn,x,y); x*=mm; x = x%maxn; if(x<0) x+=maxn; printf("%lld\n", x); } return 0; }
何玥的做法。Lucas定理,专门用于解决组合数求模问题,虽然我知道这个定理但是不会用也不打算用……证明比较复杂,可以在网上搜一下。复杂度同样不好分析,大概每次查询O(mlogm)的样子,我不是很清楚。
//by 何玥 #include <iostream> #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> using namespace std; typedef long long lld; lld n, m, p; lld Ext_gcd(lld a,lld b,lld &x,lld &y){ if(b==0) { x=1, y=0; return a; } lld ret= Ext_gcd(b,a%b,y,x); y-= a/b*x; return ret; } lld Inv(lld a,int m){ lld d,x,y,t= (lld)m; d= Ext_gcd(a,t,x,y); if(d==1) return (x%t+t)%t; return -1; } lld Cm(lld n, lld m, lld p) { lld a=1, b=1; if(m>n) return 0; while(m) { a=(a*n)%p; b=(b*m)%p; m--; n--; } return (lld)a*Inv(b,p)%p; } int Lucas(lld n, lld m, lld p) { if(m==0) return 1; return (lld)Cm(n%p,m%p,p)*(lld)Lucas(n/p,m/p,p)%p; } int main() { while(scanf("%lld%lld",&n,&m)!=EOF) { printf("%d\n",Lucas(n,m,1000000007)); } return 0; }
最后是我的做法……线性预处理出阶乘以及阶乘关于模的逆元,然后相乘即可;其中线性预处理逆元感谢昂神指点(我以前的板子是利用费马小定理做快速幂,复杂度是O(mlogm)的)。预处理复杂度O(m),查询复杂度O(1),是满分做法中理论复杂度最优且代码最短的……(身为ACMer必然是各种板子都写过了Orz)
//by wjfwzzc #include<cstdio> using namespace std; const int MAXN=500005; const int INF=1000000007; long long fac[MAXN],invfac[MAXN]; void init() { fac[0]=fac[1]=invfac[0]=invfac[1]=1; for(int i=2; i<MAXN; ++i) { fac[i]=fac[i-1]*i%INF; invfac[i]=(INF-INF/i)*invfac[INF%i]%INF; } for(int i=2; i<MAXN; ++i) invfac[i]=invfac[i-1]*invfac[i]%INF; } inline long long C(int m,int n) { if(m<0||n<0||m<n) return 0; return fac[m]*invfac %INF*invfac[m-n]%INF; } int main() { init(); int m,n; while(~scanf("%d%d",&m,&n)) printf("%lld\n",C(m,n)); }
以上就是全部题解了。如果做练习赛的人能够再多一些就好了……(大概我又想多了?)
相关文章推荐
- 第一次练习赛解题报告及标程
- 第三次练习赛解题报告及标程
- 第五次练习赛解题报告及标程
- 第六次练习赛解题报告及标程
- 一月17日新生冬季练习赛解题报告H.龟兔赛跑
- C2第四次作业解题报告
- 20111023练习赛解题报告
- XTU (湘潭大学) 2011 新生练习赛(第一场)/ 解题报告 4.4
- 【九度】2014年王道论坛研究生机试练习赛第二场解题报告
- XTU (湘潭大学) 2011 新生练习赛(第一场)/ 解题报告 4.4
- 第二次上机赛解题报告及标程
- CCNU 2010新生练习赛(DP)[差很多的解题报告]
- HNU 暑假练习赛-0902-Sleeping at Work 解题报告
- 2013级数据结构第二次练习赛解题报告
- 一月24日新生冬季练习赛解题报告A.通报批评
- 数论基础练习赛-解题报告
- 一月24日新生冬季练习赛解题报告B.字符串判等
- 第五次上机赛解题报告及标程
- 第七次练习赛解题报告及标程
- 牛客练习赛8解题报告