Problem A | 小学问题(难) |
Problem B | 一元三次方程求根(简单) |
Problem C | Vigenère密码(简单) |
Problem D | 昂贵的奖励(难) |
Problem E | 质数环(中等) |
Problem F | Lucky Word(简单) |
Problem G | 转圈游戏(中等) |
Problem H | 数的统计(简单) |
Problem I | 佳佳的课程设计(简单) |
Problem J |
以上是题目难度分布(个人观点),本次简单题居多,但很多人都不做简单题,一头啃在难题上了。【附上个人代码 并非标程】大家有更好的想法可留言一起探讨!
【A 题】 动态归划 或记忆化搜索
根据题意,我们可以想到每两个数间不是乘号就是加号,所以我们只需要考虑乘号数即可。
以前i个数间插j个乘号为状态,f[i,j]记录前i个数间插入j个乘号的最大值。
得状态转移方程:f[i,j]=max{f[k,j-1]*sum[k+1,i]|j>=1,sum[1,i]|j=0}
其中sum[i,j]表示第i个数到第j个数的和。
也许这种方程少考虑一种情况,即a*b+c,这种转移,即f[k,j]+sum[k+1,j];
但由于a,b,c都是整数,所以a*b+c<a*(b+c),所以最优情况一定是
(a1+a2+..+an)*( b1+b2+...+bn )*...这种情况,所以方程可以少考虑一种转移
——————————————————————————————————————
记忆化搜索
对[i,j]区间加num个乘号,最优是max{[i,k]加t个*[k+1,j]加num-t-1个}
表示记忆化搜索比较好想,可以很轻松的做此类区间动态规划,但得特殊考虑下乘号数等于n-1的情况。
#include <stdio.h>
#include <string.h>
const int M = 20;
int sum[M],f[M][M][M];
int max (int a,int b)
{
return a > b ? a : b;
}
int DP(int l,int r,int n)
{
if (f[l][r][n] > -1)
return f[l][r][n] ;
if (l == r || !n)
return f[l][r][n] = sum[r] - sum[l-1];
for (int t = 0;t < n;t ++)
for (int k = l;k < r;k ++)
f[l][r][n] = max (f[l][r][n],DP(l,k,t)*DP(k+1,r,n-t-1));
return f[l][r][n];
}
int main ()
{
int n,num,k,i,ans;
while (~scanf ("%d%d",&n,&k))
{
sum[0] = 0;
ans = 1;
memset (f,-1,sizeof(f));
for (i = 1;i <= n;i ++)
{
scanf ("%d",&num);
sum[i] = sum[i-1] + num;
ans *= num;
}
if (k == n-1)
printf ("%d\n",ans);
else
printf ("%d\n",DP(1,n,k));
}
return 0;
}
【B题】 二分
这题有两种方法 一是二分,二是直接模拟。
注意到题目给的提示 【记方程f(x)=0,若存在2个数x1和x2,且x12,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。】
解法一:
可用二分查找根所在的范围,过程如下。
1. 取当前可能存在解的区间(a,b);
2.若a+0.001>b或f((a+b)/2)=0,则可确定根为a+b/2并退出过程;
3.若f(a)*f((a+b)/2)<0,则可知根在区间(a,a+b/2)中,故对区间(a,a+b/2)重复该过程;
4.若f(a)*f((a+b)/2)>0,则必有f(b)*f((a+b)/2)<0也就是说根在(a+b/2,b)中,应该对此区间重复该过程。
最后,就可以得到精确到0.001的根。
解法二:根据题意 从-100到100 每次加0.001 如果满足条件就为解。
【此题就不附代码了】
【C题】 字符串处理 找规律
根据题意与图 可以找到以下规律M[i] = (C[i]-k[j]+Mod)%Mod ,大小写要跟密文一致,所以提前做一下处理及可。
#include <stdio.h>
#include <string.h>
const int M = 1005;
const int N = 105;
const int Mod = 26;
int main ()
{
int T;
char key[N],C[M];
scanf ("%d",&T);
while (T --)
{
scanf ("%s %s",key,C);
int len = strlen(key);
char flag;
for (int i = 0,j = 0; C[i] != '\0'; i ++,j ++)
{
j %= len;
if (key[j]>='a'&&key[j]<='z') key[j] -= ('a'-'A');
if (C[i]>='A'&&C[i]<='Z')
flag = 'A';
else{
flag = 'a';
C[i] -= ('a' - 'A');
}
printf ("%c",(C[i]-key[j]+Mod)%Mod+flag);
}
printf ("\n");
}
return 0;
}
【D题】贪心+高精度
这题略难 贪心应该很容易能看得出来,注意一下数据量 10000的1000次方,64位是存不下来的,得用高精度;高精度写起来比较恶心。
贪心:假设任意两个相邻的人手上的数分别为 a1,b1;a2,b2;
则 a1/b2 < a2/b1;这样的排序肯定能使最大值最小 ,转化一下 即a1*b1 < a2*b2,
所以以a*b 从小到大排序就行了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1001
using namespace std;
struct Person
{
int a, b;
} p[MAXN];
struct High
{
int len;
int num[4001];
};
int n;
bool operator < (Person n1, Person n2)
{
return n1.a * n1.b < n2.a * n2.b;
}
bool operator > (High n1, High n2)
{
if (n1.len > n2.len)
return true;
if (n1.len < n2.len)
return false;
for (int i = n1.len - 1; i > -1; --i)
{
if (n1.num[i] > n2.num[i])
return true;
if (n1.num[i] < n2.num[i])
return false;
}
return true;
}
High operator * (High n1, int number)
{
int k = 0;
n1.len += 5;
for (int i = 0; i < n1.len; ++i)
n1.num[i] *= number;
for (int i = 0; i < n1.len; ++i)
{
n1.num[i] += k;
k = n1.num[i]/10;
n1.num[i] %= 10;
}
while (n1.num[n1.len - 1] == 0)
--n1.len;
return n1;
}
High operator / (High n1, int number)
{
High ans;
memset(ans.num, 0, sizeof ans.num);
ans.len = 0;
int now = 0;
for (int i = n1.len - 1; i > -1; --i)
{
now = now * 10 + n1.num[i];
if (now < number && ans.len == 0)
continue;
ans.num[ans.len++] = now / number;
now %= number;
}
for (int i = 0; i < ans.len / 2; ++i)
swap (ans.num[i], ans.num[ans.len - i - 1]);
return ans;
}
void Give(High *n1, int number)
{
memset (n1 -> num, 0, sizeof n1->num);
n1 -> len = 0;
do
{
n1 -> num[(n1 -> len)++] = number % 10;
number /= 10;
}
while (number != 0);
}
void Print(High n1)
{
for (int i = n1.len - 1; i > -1; --i)
printf("%d", n1.num[i]);
printf("\n");
}
void Init()
{
scanf("%d", &n);
for (int i = 0; i <= n; ++i)
scanf("%d %d", &p[i].a, &p[i].b);
for (int i = 1, j; i < n; ++i)
for (j = i + 1; j <= n; ++j)
if (!(p[i] < p[j]))
swap (p[i], p[j]);
}
int main()
{
#ifdef LOCAL
freopen("data.in","r",stdin);
#endif
Init();
High s, maxx;
Give(&s,p[0].a);
Give(&maxx,0);
for (int i = 1; i <= n; ++i)
{
if (s / p[i].b > maxx)
maxx = s / p[i].b;
s = s * p[i].a;
}
Print(maxx);
return 0;
}
【E题】 搜索
这题学过 搜索的人 应该一眼就能看出来。从1~n 每个数尝试一下,如果能够成质数环 则输出,因为你是从小到大搜 所以不会出现重复的情况。注意判断无解的情况。
#include <stdio.h>
#include <string.h>
int ans[20],prime[50],vis[20];
void GetPrime()
{
for (int i = 0; i <= 50; i ++)
prime[i] = i;
prime[1] = 0;
for (int i = 2; i <= 50; i ++)
for (int j = 2; j <= 50; j ++)
if (i*j <= 50) prime[i*j] = 0;
}
bool flag;
void dfs (int dep,int n)
{
if (dep > n)
{
if (!prime[ans[1]+ans[n]])
return ;
flag = true;
for (int i = 1; i < n; i ++)
printf ("%d ",ans[i]);
printf ("%d\n",ans[n]);
return ;
}
for (int i = 2; i <= n; i ++)
{
if (!vis[i]&&prime[ans[dep-1]+i])
{
ans[dep] = i;
vis[i] = 1;
dfs (dep+1,n);
vis[i] = 0;
}
}
}
int main ()
{
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
int n,i;
GetPrime();
while (~scanf ("%d",&n))
{
ans[1] = 1;
flag = false;
memset (vis,0,sizeof(vis));
vis[1] = 1;
dfs(2,n);
if (!flag) printf ("No Answer\n");
}
return 0;
}
【F题】 简单题
这题没有卡时间 ,但不知道比赛的时候怎么那么多人TLE
建一个num[] 数组记录每个字母出现的次数,然后找出最大的和最小的相减 判断一下是不是素数就OK了
这里写一两种求素数的快速算法吧
1、 prime[i] 非0 表示是素数
void GetPrime(int n)
{
for (int i = 0;i < n;i ++)
prime[i] = i;
prime[1] = 0;
for (int i = 2;i < n/2;i ++)
for (int j = i;j < n/2;j ++)
if (i * j > n) break;
else prime[i*j] = 0;
}
2、 prime[i] == 0 为素数 跟第一种差不多
void GetPrime(int n)
{
memset (prime,0,sizeof(prime));
for (int i = 2; i <= n; i ++)
for (int j = i*2; j <= n; j += i)
prime[j] = 1;
}
3、prime[] 保存所有的素数
void GetPrime(int n)
{
int m = sqrt(n+0.5);
int c = 0;
memset (vis,0,sizeof(vis));
for (int i = 2; i <= m; i ++)
if (!vis[i])
{
prime[c++] = i;
for (int j = i*i; j <= n; j += i)
vis[j] = 1;
}
}
【G题】 快速幂
注意一下k的范围 10^9 如果用普通的方法求 10^k%Mod 肯定会超时。
X号人实际转的次数为 tmp=10^k mod n,然后ans=(tmp*m+x) mod n 就这么简单。注意得用long long
#include <stdio.h>
#define LL long long
LL Power(LL a,LL b,LL c)
{
LL ans = 1;
a %= c;
while (b > 0)
{
if(b%2) ans = (ans*a)%c;
b /= 2;
a = (a*a)%c;
}
return ans;
}
int main ()
{
#ifdef LOCAL
freopen("circle.in","r",stdin);
#endif
LL n,m,k,x;
while (~scanf ("%lld%lld%lld%lld",&n,&m,&k,&x))
{
LL tmp = Power(10,k,n);
LL ans = (x+m*tmp)%n;
printf ("%lld\n",ans);
}
return 0;
}
【H题】递推
这题应很容易就想到用递归 求解,但很可惜 数太大了,会TLE。
仔细想想会发现可以用数组把每一个数能组成的个数保存下来,求解时 只接加上就OK了,不用再往下计算。
#include <stdio.h>
#include <string.h>
int a[1005];
int main()
{
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
a[1]=1,a[2]=2;
for(int i=3;i<=1000;i++)
{
a[i]=1;
for(int j=1;j<=i/2;j++)
a[i]+=a[j];
}
int T,n;
scanf ("%d",&T);
while (T --){
scanf ("%d",&n);
printf ("%d\n",a[n]);
}
return 0;
}
【I题】模拟题
这题是模拟队列,建一个循队列 每个进程进一次队列时间片减1,当为0时不再进队列并记录它所用的时间。最后除以n;
#include <stdio.h>
#include <string.h>
const int N = 105;
int que[N];
int BFS(int front,int rear,int n)
{
int ret = 0,tt = 0;
while (front != rear)
{
int u = que[front ++];
front %= (n+1);
tt ++,u --;
if (u == 0)
ret += tt;
else {
que[rear++] = u;
rear %= (n+1);
}
}
return ret;
}
int main ()
{
#ifdef LOCAL
freopen("data1.in","r",stdin);
#endif
int n,a,cnt = 0;
while (~scanf ("%d",&n))
{
int rear = 0,front = 0;
for (int i = 0;i < n;i ++){
scanf ("%d",&a);
que[rear++] = a;
}
printf ("Case #%d: %.3f\n",++cnt,BFS(front,rear,n)*1.0/n);
}
return 0;
}
【J题】排序
赤裸裸的结构体排序,以它们的频度排序输出就OK,不管你用冒泡还是快排。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105;
struct Data
{
char str[25];
int ff;
}d[N];
bool cmp(const Data &a,const Data &b)
{
if (a.ff > b.ff) return true;
if (a.ff == b.ff&&strcmp(a.str,b.str) < 0)
return true;
return false;
}
int main ()
{
int n = 0;
while (~scanf ("%s %d",d[n].str,&d[n].ff))
n ++;
sort (d,d+n,cmp);
for (int i = 0;i < n;i ++)
printf ("%s\n",d[i].str);
return 0;
}
【以上只是个人观点,未能保证100%正确,有错的欢迎指出,有更好的解法的,可留言,一起探讨学习】