估计得分:100+100+40+100+100
实际得分:100+20+40+100+100
T1:回文词
题目描述:回文词是一种对称的字符串——也就是说,一个回文词,从左到右读和从右到左读得到的 结果是一样的。任意给定一个字符串,通过插入若干字符,都可以变成一个回文词。你的任务是写 一个程序,求出将给定字符串变成回文词所需插入的最少字符数。
比如字符串“Ab3bd”,在插入两个字符后可以变成一个回文词(“dAb3bAd”或“Adb3bdA”)。 然而,插入两个以下的字符无法使它变成一个回文词。
输入格式:
文件的第一行包含一个整数N,表示给定的字符串的长度,3≤N≤5000。
文件的第二行是一个长度为N的字符串。字符串仅由大写字母“A”到“Z”,小写字母“a”到 “z”和数字“0”到“9”构成。大写字母和小写字母将被认为是不同的。
输出格式:
输出文件名是PALIN.OUT,文件只有一行,包含一个整数,表示需要插入的最少字符数。
样例输入:
5 Ab3bd
样例输出:
2
数据范围:
3≤N≤5000
估计得分:100
实际得分:100
思路分析:经过分析题目实际上是在找最长的非连续的回文字串,
那么来看一个数据Ab3bd,我们将其反置db3bA,会发现b3b实际是相同的,而且是在我们反置之后是相同的,那么就是说
这个字符串互为回文串了,那么根贪心思想,我们只要找到最长的回文的为c,再用len-c,那么就可以得出答案了
代码:
#include<cstring> #include<algorithm> #include<cstdio> using namespace std; char A[5000+10]; char B[5000+10]; int dp[5000+10][5000+10]; int main(){ int n; scanf("%d",&n); scanf("%s",A+1); for (int i=1;i<=n;i++){ B[n-i+1]=A[i]; } for (int i=1;i<=n;i++){ for (int j=1;j<=n;j++){ if(A[i]==B[j]){ dp[i][j]=dp[i-1][j-1]+1; } else { dp[i][j]=max(dp[i][j-1],dp[i-1][j]); } } } printf("%d\n",n-dp[n][n]); return 0; }
T2:取石子游戏
题目描述:
甲乙两人面对若干堆石子,其中每一堆石子的数目可以任意确定。例如图1所示的初始局面:共n=3堆,其中第一堆的石子数a1=3,第二堆石子数a2=3,第三堆石子数a3=1。两人轮流按下列规则取走一些石子,游戏的规则如下:
○1每一步应取走至少一枚石子;
○2每一步只能从某一堆中取走部分或全部石子;
○3如果谁无法按规则取子,谁就是输家。
图 1 游戏的一个初始局面
输入格式:
输入第一行N,表示游戏的局数,以下N行是每局的初始状态,每局状态由若干堆石子组成,每堆石子由一个数字表示;输出格式:
输出共N行,每行对应一局先手是否有必胜策略,必胜为1,否则为0样例输入:
2 4 4 3 3 1
样例输出:
0 1
数据范围:
n<10#include<cstdio> #include<algorithm> #include<cmath> using namespace std; int main(){ long long n; long long ans; long long x; char c; scanf("%lld",&n); while(n--){ ans=0; for (;;){ scanf("%lld",&x); ans^=x; c=getchar(); if(c!=' ') break; } if(ans) printf("1\n"); else printf("0\n"); } return 0; }
T3:步步为零游戏
题目描述:
步步为零游戏(数据缺失)
游戏者在一张特殊的表格中按照规则跳动,使得跳到的数字经过加号和减号的连接,尽可能的逼近零。表格通常是如图1.1所示的形状,大小由中间一行的方格数N决定(图1.1就是一个N=4的例子)。游戏者从最下面的方格出发,只能向上一层有相邻边的方格跳动,按照如图1.2所示的规则在表格中跳动。当游戏者跳到最顶端的方格时,游戏结束。在游戏未结束前,游戏者不允许跳到表格外。
将游戏者跳到的2N-1个数字依次写下来,在每两个相邻的数字中间加上加号或减号,使得计算结果最接近零。
例如对于图1所示的表格,最好的跳动及计算方案是:
7+8+(-5)+(-2)-5-1-2=0
或7+10+(-7)-6+(-3)-3+2=0
或7+10+(-5)-10-5+1+2=0
或7+10+(-5)+(-2)-5-3-2=0。
输入格式:
输入文件的第一行是N,接下来2N-1行给出了表格中每行的每个方格中的数字,第i+1行的第j个数字对应于表格中第i行的第j个数字。文件中第二行的数字表示的是表格顶端的方格中的数字。文件中所有的数字都是整数,同一行相邻的两个数字间用空格符隔开。输出格式:
输出文件只有一行,是你所求出的最接近零的计算结果的绝对值。样例输入:
4 2 3 1 -3 5 7 6 10 -2 20 -7 -5 -8 10 8
样例输出:
0
数据范围:
表格中的所有数字大于等于-50,小于等于50。n<=30
估计得分:40
实际得分:40
总结:要加强dp训练
思路分析:
代码:
T4:排行榜
题目描述:小迈克尔住在一个小镇上,他喜欢看每周日下午发布的音乐电视评比。它每周都根据选票介绍相同的歌曲,列出这些歌曲的流行排行榜。
有一个星期日迈克尔和他的朋友在一起玩得太久了以致于未能看到新的流行榜。他非常失望,但是不久他就发现下周至少可以部分地建立出流行榜。除了每首歌曲的位置,排行榜还根据这些歌曲上周的排行列出了它们排行变动的信息,更精确地说,从这周起,不管那首歌是继续排在原位,还是排名上升或排名下降,都会给出一点说明。
编写程序,根据给定的流行榜帮助迈克尔推断出上周可能的排行榜。
输入格式:
输入文件的第一行是一个整数N,1≤N≤100,表示排行榜上歌曲的总数。接下来的N块列出了排行信息。每块有两行组成,第i块第一行是第i首歌曲的名称,歌名包括最多不超过100个英文大写字母,第二行包含下列三个单词中的一个:UP(歌曲在排行榜上的位置上升),DOWN(歌曲在排行榜上的位置下滑)或SAME(排行不变),表示与上周排行榜相比,排行榜所发生的变动。
输出格式:
输出文件应该用N行输出一个上周可能的排行榜。每一行包含一首歌名,即第i行包含排行榜上第i首歌的歌名。
注意:解不必是唯一的,但对于每一个测试数据都至少有一个解。
样例输入:
5 HIGHHOPES UP LOWFEELINGS UP UPANDDOWN DOWN IAMSTILLSTANDING DOWN FOOLINGAROUND DOWN
样例输出:
UPANDDOWN IAMSTILLSTANDING FOOLINGAROUND HGHHOPES LOWFEELINGS
数据范围:
1≤N≤100估计得分:100
实际得分:100
思路分析:通过题目可以发现,若排名下降的,那么在原来排名中肯定要上升,所以只要保证每个人排名都上升即可
那么,就可以贪心,位置越在前的排名越在前,降序排序即可,排名上升的相同处理方法
代码:
#include<cstdio> #include<algorithm> #include<string> #include<map> #include<iostream> using namespace std; map<string,int>s1; map<int,string>s2; string s,dir; int tot; int vis[1000]; int up[1000],down[1000],upnum,downnum; bool cmp(const int &x,const int &y){ return x>y; } int main(){ int n; int q; scanf("%d",&n); for (int i=1;i<=n;i++){ cin>>s; if(!s1[s]){ s1[s]=++tot; q=tot; s2[tot]=s; } else { q=s1[s]; } cin>>dir; if(dir[0]=='U') up[++upnum]=q; else if(dir[0]=='D') down[++downnum]=q; else vis[q]=q; } sort(down+1,down+1+downnum); int p=0; for (int i=1;i<=downnum;i++){ while(vis[++p]){ }; vis[p]=down[i]; } p=n+1; sort(up+1,up+1+upnum,cmp); for (int i=1;i<=upnum;i++){ while(vis[--p]){ }; vis[p]=up[i]; } for (int i=1;i<=n;i++){ cout<<s2[vis[i]]<<endl; } return 0; }
T5:字符串最小循环表示
给你一个串s[0~n-1],要求你选择两个数i,j,满足0<=i<=j<=n,然后将s[0~i-1]、s[i~j-1]、s[j~n-1]翻转,要求翻转后的串字典序最小。
输入样例:
2
bacbadcba
abc
输出样例:
2 5
1 2
数据范围:
串由小写字母构成;
令S为所有串长度之和;
对于10%的数据,S<=300;
对于30%的数据,S<=2000;
对于60%的数据,S<=200000;
对于100%的数据,S<=10000000;
请使用gets或者scanf或者更快的方法读入;
数据有梯度。
估计得分:100
实际得分:100
分析:
显然正解的复杂度是O(n),但是并不会O(n)算法,直接n方暴力
首先先将s翻转一下,问题就转化为选i,j,使得将s[0..i-1] 与 s[j..n-1]的位置换一下后字典序最小。
就是先找一个后缀j,将其放到最前面,再在剩下的部分找一个后缀i,将其放到剩下的这一部分的前面。
我们可以先枚举j,然后对剩下的部分做最小表示法
时间复杂度O(n^2)
预计得分:30
实际得分:30
改进:
观察上面的算法,我们可以发现这个方法的耗时部分在于枚举j,这里我们可以用一种玄学的方式去求j:
先做一遍最小表示法,求出T,再做一遍kmp,求出S[T..n]中最短的后缀等于前缀的串X,再求S的后缀中由X循环构成的最长的重复子串,该串就是后缀j,在对于剩下的部分一遍最小表示法就行了。
比如把S翻转后串是“acababab”,那么最小表示法求出的T就是3,然后就是求“ababab” 中最短的后缀等于前缀的串,就是“ab”,再求其最长的由“ab”构成的后缀重复子串,就是“ababab”,所以j就是3,再对S[1..2]来一遍最小表示法就OK了。
证明:见https://www.xuebuyuan.com/3184845.html;
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; const int maxn=10000011; char s[maxn];int n; void read(){ scanf("%s",s+1);n=strlen(s+1); } int xx(char *s,int n){ int i,j,k; for (i=1,j=2,k=0;i<=n && j<=n && k<n;++k){ if (s[i+k>n?i+k-n:i+k]>s[j+k>n?j+k-n:j+k]) i+=k+1,i+=(i==j),k=-1; else if (s[i+k>n?i+k-n:i+k]<s[j+k>n?j+k-n:j+k]) j+=k+1,j+=(i==j),k=-1; } return min(i,j); } int next[maxn]; void kmp(char *s,int n){ next[1]=0; for (int i=2,j=0;i<=n;++i){ while (j && s[j+1]!=s[i]) j=next[j]; if (s[i]==s[j+1]) next[i]=++j;else next[i]=j; } } void work(){ reverse(s+1,s+n+1); int t=xx(s,n),x=n-t+1; kmp(s+(t-1),x); while (next[x]) x=next[x]; int m=n-x+1,k,len=x;bool ok=1; for (k=m-len;k>0 && ok;k-=len) for (int i=0;i<len;++i) if (s[k+i]!=s[m+i]) ok=0; m=k+len; if (!ok) m+=len; printf("%d %d\n",n-m+1,n-xx(s,m-1)+1); } int main(){ int t=1;scanf("%d",&t); while (t--) read(),work(); return 0; }