2018年计算机科学与工程学院新生赛

ACM新生赛总结,加油!

A#

326. CC玩游戏
只需判断$T+\sum_{i=1}^nA[i]\le K$是否成立即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;
int main(){
int n,t,k;
cin>>n>>t>>k;
int sum=0;
for(int i=1;i<=n;i++){
int x;
cin>>x;
sum+=x;
}
if(t+sum<=k)cout<<"CCHandsome!\n";
else cout<<"PoorCC!";
return 0;
}

B#

327. CC的木棍
推出答案的函数表达式,容易看出单调性。
二分求函数零点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <cmath>
using namespace std;
double x,y,c;
const double eps=1e-9;
double f(double m){
double tx=(x*x-m*m),ty=(y*y-m*m);
double ans=tx*ty-c*c*(tx+ty+2*sqrt(tx*ty));
return ans;
}
int main(){
cin>>x>>y>>c;
double l=0,r=min(x,y),mid;
int flag=(f(l)>0)?1:0;
while(1){
mid=(l+r)/2.0;
double temp=f(mid);
if(fabs(l-r)<eps)break;
if(fabs(temp)<eps)break;
else if(temp>0){
if(flag)l=mid;
else r=mid;
}else if(temp<0){
if(flag)r=mid;
else l=mid;
}
}
printf("%.8lf\n",mid);
return 0;
}

C#

328. CC的秘密
先预处理出[1000,9999]范围内所有的素数。
然后写大暴力,把A在12步变动以内所有的数处理一遍,看是否有B。
一开始我也是不确定多少步,通过写暴力数据测试,可以得知。
范围内任意一个素数都可以在10步以内修改得到任意另一个素数。
因此这题的Impossible是不可能出现的情况!
BFS完全没想到啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
int check[10005],can[15][10005];
int main(){
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
for(int i=1000;i<=9999;i++){
int f=1;
for(int j=2;j<=int(sqrt(i))+1;j++){
if(i%j==0){
f=0;
break;
}
}
if(f)check[i]=1;
}
int t;
cin>>t;
while(t--){
int a,b;
cin>>a>>b;
if(a==b){
cout<<"0\n";
continue;
}
memset(can,0,sizeof(can));
can[0][a]=1;
int times=0;
for(int i=1;i<=12;i++){
for(int j=1000;j<=9999;j++){
if(can[i-1][j]){
int a1=j/1000,a2=(j/100)%10,a3=(j%100)/10,a4=(j%10);
for(int k=1;k<=9;k++){
if(k==a1)continue;
int temp=k*1000+a2*100+a3*10+a4;
if(check[temp]){
can[i][temp]=1;
}
}
for(int k=0;k<=9;k++){
if(k==a2)continue;
int temp=a1*1000+k*100+a3*10+a4;
if(check[temp]){
can[i][temp]=1;
}
}
for(int k=0;k<=9;k++){
if(k==a3)continue;
int temp=a1*1000+a2*100+k*10+a4;
if(check[temp]){
can[i][temp]=1;
}
}
for(int k=0;k<=9;k++){
if(k==a4)continue;
int temp=a1*1000+a2*100+a3*10+k;
if(check[temp]){
can[i][temp]=1;
}
}
}
if(can[i][b]){
times=i;
break;
}
}
if(times)break;
}
if(times)cout<<times<<endl;
else cout<<"Impossible"<<endl;
}
return 0;
}

附上当时考场上写的数据生成程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
int check[10005];
int main(){
freopen("input.txt","w",stdout);
cout<<"100\n";
for(int i=1000;i<=9999;i++){
int flag=1;
for(int j=2;j<int(sqrt(i))+1;j++){
if(i%j==0){
flag=0;
break;
}
}
if(flag){
//printf("1009 %d\n",i);
check[i]=1;
}
}
int count=0;
for(int i=1000;i<=9999;i++){
for(int j=1000;j<=9999;j++){
if(check[i]&&check[j]){
printf("%d %d\n",i,j);
count++;
}
}
}
cout<<count<<endl;
return 0;
}

D#

329. CC的画板

E#

330. CC锤小兵
当时没想到前缀和+后缀和的做法。
用了一种类似数位dp的方法。
f[i][j][0]表示前i-1个数第j位是否有0
f[i][j][1]表示前i-1个数第j位是否有1
g[i][j]类似表示第i个数后的数。
感觉和前缀后缀类似,但是写法要麻烦一点。
这样可以处理位与和位或。
由于异或的逆元是本身。
故可以先把所有数异或一下,再异或a[i]就可以得到除a[i]外所有数异或的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <cstdio>
using namespace std;
const int N=100005;
int a[N],p[N],Xor,f[N][12][2],g[N][12][2];
int main(){
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
Xor=a[1];
for(int i=2;i<=n;i++){
Xor=Xor^a[i];
}
for(int i=1;i<=m;i++){
cin>>p[i];
}
for(int i=2;i<=n;i++){
for(int j=0;j<=10;j++){
f[i][j][0]=f[i][j][0]||f[i-1][j][0]||(((a[i-1]>>j)&1)==0);
f[i][j][1]=f[i][j][1]||f[i-1][j][1]||(((a[i-1]>>j)&1)==1);
//printf("f[%d][%d][0]=%d,f[%d][%d][1]=%d\n",i,j,f[i][j][0],i,j,f[i][j][1]);
}
}
for(int i=n-1;i>=1;i--){
for(int j=0;j<=10;j++){
//printf("(a[%d]>>%d)&1=%d\n",i+1,j,(a[i+1]>>j)&1);
g[i][j][0]=g[i][j][0]||g[i+1][j][0]||(((a[i+1]>>j)&1)==0);
g[i][j][1]=g[i][j][1]||g[i+1][j][1]||(((a[i+1]>>j)&1)==1);
//printf("g[%d][%d][0]=%d,g[%d][%d][1]=%d\n",i,j,g[i][j][0],i,j,g[i][j][1]);
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=10;j++){
f[i][j][0]=f[i][j][0]||g[i][j][0];
f[i][j][1]=f[i][j][1]||g[i][j][1];
//printf("f[%d][%d][0]=%d,f[%d][%d][1]=%d\n",i,j,f[i][j][0],i,j,f[i][j][1]);
}
}
int w=0;
for(int i=1;i<=m;i++){
for(int j=0;j<=10;j++){
if(f[p[i]][j][0]==0/*&&f[p[i]][j][1]*/){
w+=(1<<j);
//printf("&&when i=%d,j=%d,w=%d\n",i,j,w);
}
if(f[p[i]][j][1]==1){
w+=(1<<j);
//printf("||when i=%d,j=%d,w=%d\n",i,j,w);
}
/*if((f[p[i]][j][0]==1)&&(f[p[i]][j][1]==1)){
w+=(1<<j);
printf("^^when i=%d,j=%d,w=%d\n",i,j,w);
}*/
}
w+=Xor^a[p[i]];
}
cout<<w<<endl;
return 0;
}

F#

331. CC看星星
一个简单又重要的结论:最大的斜率一定在两个x坐标相邻的点连接产生
画图易证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
struct point{
int x,y;
}a[N];
bool cmp(point x,point y){
return x.x<y.x;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].x,&a[i].y);
}
sort(a+1,a+1+n,cmp);
double maxk=-1e9;
for(int i=1;i<n;i++){
double temp=double(a[i].y-a[i+1].y)/(a[i].x-a[i+1].x);
if(temp>maxk)maxk=temp;
}
printf("%.14lf\n",maxk);
return 0;
}

G#

332. CC的单向路
构造题((⊙o⊙)…)
边只能从到达点数多的点连向到达点数少的点。
做法参考题解:
先按A[i]排序,从小开始,若某个点到达的点数比A值小于它的点数更大,则无解。
否则连A[i]条单向边指向前A[i]个点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <bits/stdc++.h>
using namespace std;
struct node{
int index,reach;
node(int a,int b):index(a),reach(b){}
};
struct edge{
int u,v;
edge(int u,int v):u(u),v(v){}
};
bool cmp(node x,node y){
return x.reach<y.reach;
}
vector<node> G;
vector<edge> E;
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
G.push_back(node(i,x));
}
sort(G.begin(),G.end(),cmp);
int f=1,m=0;
for(int i=0;i<n;i++){
int order=i;
while(order>0&&G[order].reach==G[order-1].reach)order--;
if(order<G[i].reach){
f=0;
break;
}
for(int j=0;j<G[i].reach;j++){
E.push_back(edge(G[i].index,G[j].index));
m++;
}
}
if(f==0)puts("-1");
else{
printf("%d\n",m);
for(auto i:E){
printf("%d %d\n",i.u,i.v);
}
}
return 0;
}

H#

333. CC的速度
简单的dp。
显然CC的疲劳值先增后减,并且一定会减到0。
由于最后的疲劳值一定为0,从后往前分析。
0这个状态一定从之前一个0的状态转移而来。
故枚举所有可转移的状态,取最优解。
利用前缀和计算区间和。
f[i]表示在第i分钟结束后,CC的疲劳值一定是0的情况下,最远能冲多远。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=10005;
const int M=505;
int d[N],f[N],sum[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>d[i];
sum[i]=sum[i-1]+d[i];
}
for(int i=1;i<=n;i++){
f[i]=max(f[i],f[i-1]);
for(int j=0;j<=m;j++){
if(i-(j<<1)>=0)f[i]=max(f[i],f[i-(j<<1)]+sum[i-j]-sum[i-(j<<1)]);
}
}
cout<<f[n]<<endl;
return 0;
}

I#

334. CC好厉害
利用的数学技巧。
主要是对数的两个性质:

$$ \log_ab^n=n\log_ab\\ \log_abc=\log_ab+log_ac $$
由于任意一个数都可以用科学计数法表示。 如$10234432=1.0234432 \times 10^7$,则$\log_{10}(10234432)=\log_{10}(1.0234432)+7$ 那么$\log_{10}(1.0234432)$就是$\log_{10}(10234432)$的小数部分。 因此,若要计算$X^n$的前$k$位,可以先取对数,把指数降成乘积。 $\log_{10}X^n=n\times \log_{10}X$,再取出这个数的小数部分$Y$(整数部分不影响前$k$位的值) 计算$10^Y$,就可以得到用小数表示的结果。 显然$10^{Y+k-1}$的整数部分就是$X^n$的前$k$位。 下面用了fmod(X,1)来求X的小数部分。
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int M=3;
int main(){
//cout<<fmod(2.71828,1);
long long n,k;
cin>>n>>k;
cout<<int(pow(10,M-1+fmod(k*log10(n),1)))<<endl;
return 0;
}

J#

335. CC的金手指
容易推出ab可以互换位置,而c无法变化。
故先判断c的数量,再以c为分界点判断每个区间内的ab数量的奇偶性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <bits/stdc++.h>
using namespace std;
char s1[10005],s2[10005];
int main(){
int n;
cin>>n>>s1>>s2;
int c1=0,c2=0;
for(int i=0;i<n;i++){
if(s1[i]=='c')c1++;
if(s2[i]=='c')c2++;
}
if(c1!=c2)puts("No");
else{
int f=1;
s1[n]=s2[n]='c';
int sc1=0,sc2=0;
while(sc1<=n&&sc2<=n){
int a1=0,a2=0,b1=0,b2=0;
while(s1[sc1]!='c'){
if(s1[sc1]=='a')a1++;
else if(s1[sc1]=='b')b1++;
sc1++;
}
while(s2[sc2]!='c'){
if(s2[sc2]=='a')a2++;
else if(s2[sc2]=='b')b2++;
sc2++;
}
if((a1&1)!=(a2&1)||(b1&1)!=(b2&1)){
f=0;
break;
}
sc1++;
sc2++;
}
if(f)puts("Yes");
else puts("No");
}
return 0;
}

参考资料#

题解
优秀代码

2018年计算机科学与工程学院新生赛

https://blog.tootal.xyz/posts/scutpc2018-summary1/

作者

黄智权

本文发布于

2018-12-01

本文更新于

2018-12-01

许可协议

评论