2015苏州大学ACM-ICPC集训队选拔赛(3)题解
1001
考虑前半组数,我们只需要标记每个数出现的次数,再加上这个数之前的数出现的次数,即为这个数在m次操作中总共需要翻转的次数(即求前缀和),再根据翻转的奇偶性判断最后这个位置上的数有没有翻转即可。后半组数的处理方法类似。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<string> 5 #include<cmath> 6 #include<algorithm> 7 #include<string> 8 #include<vector> 9 using namespace std; 10 int s[1000005]; 11 int m,n,vis[200010]={0},d[100010]={0}; 12 int main() 13 { 14 15 while(scanf("%d%d",&n,&m)!=EOF) 16 { 17 memset(vis,0,sizeof(vis)); 18 for(int i=0;i<n;i++) 19 scanf("%d",&s[i]); 20 for(int i=0;i<m;i++) 21 { 22 scanf("%d",&d[i]); 23 } 24 sort(d,d+m); 25 for(int i=0;i<m;i++) 26 { 27 vis[d[i]-1]++; 28 vis[n-d[i]]++; 29 } 30 for(int i=1;i<n/2;i++) 31 { 32 vis[i]+=vis[i-1]; 33 vis[n-i-1]+=vis[n-i]; 34 } 35 for(int i=0;i<n;i++) 36 { 37 if(i>0) 38 printf(" "); 39 if(vis[i]%2==0) 40 printf("%d",s[i]); 41 else 42 printf("%d",s[n-i-1]); 43 } 44 printf("\n"); 45 } 46 return 0; 47 }
1002
很明显环中的最大值为a与b的最小公倍数,最小值为最大公因数。需要理论证明的同学请私戳学长。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 #include<cstring> 5 #define ll long long 6 using namespace std; 7 ll Gcd(ll a,ll b) 8 { 9 return b?Gcd(b,a%b):a; 10 } 11 int main() 12 { 13 ll a,b; 14 while(scanf("%lld %lld",&a,&b)!=EOF) 15 { 16 ll gcd=Gcd(a,b); 17 ll lcm=a/gcd*b; 18 printf("%lld\n",lcm-gcd); 19 } 20 return 0; 21 }
1003
对于一个n*n的棋盘,我们分两种情况讨论:
第一种情况,n是一个奇数。那么这种时候,棋盘有一个中心点,且总格子数为偶数。
第二种情况,n是一个偶数。那么这种时候,棋盘没有中心点,且总格子数为偶数。
当n为偶数时,无论先手如何行动,后手只要保持与其 关于棋盘中心对称 放子,那么就已经是后手的必胜态。具体说明如下:
马的攻击范围,是与马成3*2或2*3的矩形的 与马对称的顶点。设n*n棋盘上每个格子坐标依次为(1,1)(1,2)…(n,n),倘若先手选择把棋子放置在(i,j)格上,那么后手相对称的放置方法为,放置在(n+1-i,n+1-j)上。现在我们来证明后手总是可 以在(n+1-i,n+1-j)上落子。
因为格子的坐标都是整数,所以不可能存在先手放置在(i,j)的马能吃到(n+1-i,n+1-j)的情况。又因为开局时棋盘是关于中心对称的,而每一回合后手总是跟随先手对称落子,所以每回合棋盘的各个格子的状态必然是关于棋盘 中心对称的。
所以易知,当先手能够把棋子放置在(i,j)格上的时候,后手总是可以在(n+1-i,n+1-j)上落子的。即,只要先手还能放,那下一步后手肯定也能放。
所以,最终先找不到合适位置落子的必然是先手。所以后手 保持与先手关于棋盘中心对称放子 是一种必胜的方法。所以先手无论如何放子,都是先手的必败态。
当n为奇数时,先手若选择放置在整张棋盘的中心点,而后跟随后手对称落子,则是先手的必胜态。具体说明方法与n为偶数时类似,不再赘述。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 #include<cstring> 5 using namespace std; 6 int main() 7 { 8 int n; 9 while(scanf("%d",&n)!=EOF) 10 { 11 if(n%2==0) 12 cout<<0<<endl; 13 else 14 cout<<1<<endl; 15 } 16 return 0; 17 }
1004
这是一道二进制题,题目上已经有提示了。n个树坑可以看成是n位二进制数,没有种树的位置上值为0,种树的位置上值为1。这样只需要枚举所有的n位二进制数,求出每一位上的值,再判断是否至少有1以及任意两个1之间是否距离至少为m。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 6 using namespace std; 7 int a[100]; 8 9 int main() 10 { 11 int n,m,k; 12 while(~scanf("%d%d%d",&n,&k,&m)) 13 { 14 int ans=0; 15 for(int i=0;i<(1<<n);++i) 16 { 17 int cnt=0; 18 memset(a,0,sizeof(a)); 19 int num=i; 20 while(num!=0) 21 { 22 a[cnt++]=num%2; 23 num=num/2; 24 } 25 int count=0,pos=1; 26 num=1; 27 bool f=true; 28 for(int j=0;j<cnt;++j) 29 { 30 if(a[j]==1) 31 { 32 if(num<m&&pos!=1) 33 f=false; 34 count++; 35 num=1; 36 pos++; 37 } 38 else 39 num++; 40 } 41 if(f&&count>=k) 42 ans++; 43 } 44 printf("%d\n",ans); 45 } 46 return 0; 47 }
1005
签到题。将所有给出的数转化成10进制数,注意a~f的情况。用一个长度为1000的数组保存每个数出现的次数,最后遍历一遍数组找到出现次数最多的数即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 6 using namespace std; 7 int num[1200]; 8 char a[10]={'a','b','c','d','e','f'}; 9 int main() 10 { 11 int t,n; 12 char s[20]; 13 while(~scanf("%d",&t)) 14 { 15 memset(num,0,sizeof(num)); 16 while(t--) 17 { 18 scanf("%d %s",&n,s); 19 int c=0; 20 for(int i=0;i<strlen(s);++i) 21 { 22 if(s[i]>='0'&&s[i]<='9') 23 { 24 c=c*n; 25 c+=s[i]-'0'; 26 } 27 else 28 { 29 c=c*n; 30 c+=s[i]-'a'+10; 31 } 32 } 33 num[c]++; 34 } 35 int max=0; 36 int ans=0; 37 for(int i=0;i<=1000;++i) 38 { 39 if(num[i]>max) 40 { 41 max=num[i]; 42 ans=i; 43 } 44 } 45 printf("%d\n",ans); 46 } 47 return 0; 48 }
1006
很经典的一道题,也算是签到题。解决这道题的方法很多,这里就只给出最优的方案。我们需要保存一个当前最大的子序列和maxsun以及从1开始的子序列的和thissum。当thissum>maxsum时,maxsum赋值为thissum,当thissum<0时,thissum赋值为0。这样遍历一遍数组,thissum每次加上遍历到的数,并进行更新,最后maxsum保存的便是最大的子序列和。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 using namespace std; 6 7 int main() 8 { 9 long long ThisSum,MaxSum,i,a,n; 10 while(~scanf("%d",&n)) 11 { 12 13 ThisSum = MaxSum = 0 ; 14 for( i=0 ; i<n ; i++ ) 15 { 16 scanf("%I64d",&a); 17 ThisSum += a; 18 19 if( ThisSum > MaxSum ) 20 MaxSum = ThisSum ; 21 else 22 if( ThisSum < 0) 23 ThisSum = 0 ; 24 } 25 printf("%I64d\n",MaxSum); 26 } 27 return 0; 28 }
1007
这道题是鼓励大家勇于提交的,给的两串数其实是没有规律的。而测试样例只有一组,那么唯一的可能就是测试样例在已经给出的数对里面。那么只要判断读入的字符串究竟是其中的哪一个就行了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<string.h> 3 char a[][12]={ 4 "0000000001", 5 "0000000011", 6 "0000000100", 7 "0000000101", 8 "0000000110", 9 "0000000111", 10 "0000001000", 11 "0000001001", 12 "0000001010", 13 "0000001011" 14 }; 15 char b[][12]={ 16 "1000000000", 17 "1100000000", 18 "0001000000", 19 "1001000001", 20 "0011000100", 21 "1101000000", 22 "0001001000", 23 "1000100010", 24 "1000100000", 25 "1001100000" 26 }; 27 int main(){ 28 char s[12]; 29 scanf("%s",s); 30 for(int i=0;i<10;i++){ 31 if(strcmp(s,a[i])==0) 32 puts(b[i]); 33 } 34 }
1008
这道题是对凯撒加密的频数攻击,即依次枚举26个密钥,尝试用这些密钥将文章解密,得到26篇文章,然后比较这26篇文章与统计规律进行比较,找出最符合要求的文章即可。
重点是如何从26篇文章中筛选出最接近统计规律的文章。通常用的是如下的方法:
1、设p[i]表示26个字母中第i个字符的统计规律里的频率,我们计算所有26个字母对应的p[i]^2之和,这个值大约是0.065
2、设q[i]表示用当前密钥k解密后的文章中第i个字符的实际频率,我们计算sigma(p[i]*q[i])得到一个相关值m
3、对于26个密钥,找出相关值最接近0.065的那个密钥,那么这个就是最后的结果。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cmath> 3 #include<cstring> 4 char c[100000000]; 5 double table[27]={0.08167,0.01492,0.02782,0.04253,0.12702,0.0228,0.02015,0.06094,0.06966,0.00153,0.00772,0.04025,0.02406,0.06749,0.07507,0.01929,0.00095,0.05987,0.06327,0.09056,0.02758,0.00978,0.02360,0.00150,0.01974,0.00074}; 6 double f[27]; 7 int cnt[27]={0}; 8 int len,num; 9 void fun(){ 10 for(int i=0;i<len;i++){ 11 if(c[i]>='a'&&c[i]<='z'){ 12 cnt[c[i]-'a']++; 13 num++; 14 }else if(c[i]>='A'&&c[i]<='Z'){ 15 cnt[c[i]-'A']++; 16 num++; 17 } 18 } 19 for(int i=0;i<26;i++){ 20 f[i]=1.0*cnt[i]/num; 21 } 22 int key; 23 double k=100; 24 for(int i=0;i<26;i++){ 25 double test=0; 26 for(int j=0;j<26;j++){ 27 test+=table[(j-i+26)%26]*f[j]; 28 } 29 if(fabs(test-0.065)<k){ 30 key=i; 31 k=fabs(test-0.065); 32 } 33 } 34 for(int i=0;i<len;i++){ 35 if(c[i]>='a'&&c[i]<='z'){ 36 putchar((c[i]-'a'-key+26)%26+'a'); 37 }else if(c[i]>='A'&&c[i]<='Z'){ 38 putchar((c[i]-'A'-key+26)%26+'A'); 39 }else{ 40 putchar(c[i]); 41 } 42 } 43 } 44 int main(){ 45 //freopen("in.txt","r",stdin); 46 len=num=0; 47 char tmp; 48 while(true){ 49 tmp=getchar(); 50 if(tmp==EOF||tmp=='#'){ 51 fun(); 52 len=num=0; 53 memset(c,0,sizeof c); 54 memset(cnt,0,sizeof cnt); 55 if(tmp==EOF) 56 break; 57 putchar('#'); 58 }else{ 59 c[len++]=tmp; 60 } 61 } 62 }
1009
基本照着题目的要求写即可,有以下几个注意点:
1、1也是个无聊的数
2、输入的x和y的大小不确定,需要先比较一下
3、对于能表示成a^2+b^2的这个条件,实际上我们可以证明,一个奇素数,如果能够表示成为上述形式,那么当且仅当他模4的余数为1。如果没发现这个定理,其实打表也是可以卡时间过的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 using namespace std; 3 const int maxi=1000001; 4 bool vis[maxi]; 5 int sum[maxi]; 6 int main(){ 7 sum[0]=0; 8 sum[1]=1; 9 for(int i=2;i<maxi;i++){ 10 sum[i]=sum[i-1]; 11 if(!vis[i]){ 12 if((i%4==1||i==2)&&i%5!=4){ 13 sum[i]++; 14 } 15 for(int j=1;i*j<maxi;j++){ 16 vis[i*j]=true; 17 } 18 } 19 } 20 int a,b; 21 while(scanf("%d%d",&a,&b)==2){ 22 if(a>b){ 23 printf("%d\n",sum[a]-sum[b-1]); 24 }else{ 25 printf("%d\n",sum[b]-sum[a-1]); 26 } 27 } 28 }
1010
一道模拟题,按照要求写,但是如果对每个样例都循环k次,那么这显然会超时。实际上对于每组输入并不需要循环这么多次,一般循环几次后就会看到循环的终点了,这里有两个终点,153和0,循环到这里就可以停了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 using namespace std; 6 const int maxi=1000001; 7 int fun(int n,int k){ 8 int cnt=0; 9 while(true){ 10 if(n==0) 11 return 0; 12 if(n==153) 13 return 153; 14 n-=n%8; 15 n+=n%3==0?0:(3-n%3); 16 int tmp=0; 17 while(n>0){ 18 tmp+=(n%10)*(n%10)*(n%10); 19 n/=10; 20 } 21 n=tmp; 22 cnt++; 23 if(cnt==k) 24 return n; 25 } 26 } 27 int main(){ 28 int n,k; 29 while(scanf("%d%d",&n,&k)==2) 30 printf("%d\n",fun(n,k)); 31 }