今天湖南的一场比赛,题目是2013 ACM ICPC Southeast USA Regional Programming Contest。
我们三个水货按照正式比赛的方式,一台机打题,另外两台只看题。很有feel,最后出4题,排第四。简单总结一下。
我首先看的是最后一题,You Win!。看了一眼就知道是状态压缩DP。状态记录多少字母已经打出,以及最后一个字母的位置。略微计算一下复杂度,大概2^18*18*18,给点时间是20S,应该没什么问题。敲完后Debug了一会儿,交了一发,Runtime Error。数组开小了,再来一发,TLE……嗯,有点不爽了。中间可能超时的就是计算从上一次光标的位置到准备打的光标的位置的距离计算。预处理一张300000*20的表,记录每种状态光标移动时的距离,再来一发,10S多A了。代码如下:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int dp[300000][20]; int itable[300000][20]; int ctable[200][200]; void calI() { for(int i=0;i<(1<<18);i++) { for(int j=1;j<=18;j++) { itable[i][j] = itable[i][j-1]+(i&(1<<(j-1))?1:0); } } for(int i='A';i<='Z';i++) { for(int j=i;j<='Z';j++) { ctable[j][i]=ctable[i][j]=min(j-i, i-'A'+'Z'-j+1); } } } int main() { calI(); char str[20]; while(~scanf("%s",str) && str[0]!='0') { int len = strlen(str); memset(dp,0x7f,sizeof(dp)); dp[0][0] = 0; for(int i=1;i<(1<<len);i++) { for(int j=0;j<len;j++) if(i&(1<<j)) { for(int k=0;k<=len;k++) if(dp[i^(1<<j)][k]!=0x7f7f7f7f) { int t=0; if(k>j) { t+=itable[i][k]-itable[i][j+1]; } else { t+=itable[i][j]-itable[i][k]; } if(k==0) { t+=ctable['A'][str[j]]; } else { t+=ctable[str[j]][str[k-1]]; } dp[i][j+1]=min(dp[i][j+1], dp[i^(1<<j)][k]+t+1); } } } int ans=0x7f7f7f7f; for(int i=0;i<=len;i++) ans=min(dp[(1<<len)-1][i], ans); printf("%d\n", ans); } }比赛的时候代码控制的不是非常美观,这是需要改进的地方。熟练度的确不够,遇到这种问题具体细节思考的时间很长。
再然后,就是E题 Skyscrapers。类似于最长上升子序列,但是不是,高建筑会挡掉矮建筑。
这题也想了很久,最后还是A掉了。首先,我们必然能看到最高的建筑,高度为n。我们将建筑分为两组,n左边一组,右边为一组。按照顺序排好。比如左边的一组,共5个建筑物:a1 a2 a3 a4 a5。a1<a2<a3<a4<a5。如果左边的要求是看到3个建筑,那么a5必然能看到,a4也必然能看到,因为没有东西可以挡它。对于a3,可以放到a4后面,一种放法。对于a2,无论a3有没有放在a4后面,都是两种放法。对于a1,同样,有3种放法。我们只需要选择两个建筑物隐藏起来,所以情况数为:1*2+2*3+1*3。简单来说,n个建筑,看到m栋,放法是(1,2,3。。。n-2)中选择(n-m)个数的乘积的和。当时也是卡在这里地方,和队友讨论了一下,可以递推。开一张5000*5000的表,预处理即可。代码如下:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long LL; const int mod = 1000000007; int sum[5010][5010]; int C[5010][5010]; int powMod(LL a,int b) { a%=mod; LL res = 1; while(b) { if(b&1) res=(res*a)%mod; b>>=1; a=a*a%mod; } return (int)res; } void initSum() { for(int i=0;i<=5000;i++) sum[0][i]=1; for(int i=1;i<=5000;i++) { for(int j=i;j<=5000;j++) { sum[i][j]=(sum[i][j-1]+(long long)sum[i-1][j-1]*j%mod)%mod; } } for(int i=0;i<=5000;i++) { C[i][0]=1; for(int j=1;j<=i;j++) { C[i][j] = (C[i-1][j]+C[i-1][j-1])%mod; } } } int cal(int n,int m) { if(m>n) return 0; if(m==n) return 1; if(m==1 && n!=1) return 0; int t = n-2; int sel = n-m; return sum[sel][t]; } int main() { initSum(); int n,l,r; while(~scanf("%d%d%d",&n,&l,&r) && (n||r||l)) { LL res = 0; for(int i=l;i<=n-r+1;i++) { res = (res+(long long)C[n-1][i-1]*cal(i,l)%mod*cal(n-i+1,r)%mod)%mod; } printf("%d\n", (int)res); } }同样,比赛时的代码,可以再优化优化。这题主要是思路有点难想。
好吧,另外两题是队友A的,一道水题,一道最短路。
4题,如果在省赛那种规模,应该是银牌。差不多就是这样了,今年的省赛在两个月后,不知能做到什么样的地步。