96. 最大连续子序列和(1007)
本题要求求出最大连续子序列和即对应首尾元素。利用动态规划思想,关键在于如何确定状态转移方程。
考虑以A[i]为结尾的最大连续序列和数组dp[i],则dp[i]=max{A[i],dp[i-1]+A[i]},又有边界条件A[0]=dp[0],即可求解。
由于还需要确定子序列范围,额外定义一个数组,保留所在连续子序列的前一结点
注意:(1)全为负数情况,输出最大和0,首尾元素
(2)全为负数或0的情况,输出0 0 0。
#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn=10010;
int A[maxn],dp[maxn],pre[maxn];
int main(){
int n;
scanf("%d",&n);
bool flag=true;//判断是否为全负
for(int i=0;i<n;i++){
scanf("%d",&A[i]);
if(A[i]>=0) flag=false;
}
if(flag){
printf("0 %d %d",A[0],A[n-1]);
return 0;
}
dp[0]=A[0];
pre[0]=0;
for(int i=1;i<n;i++){
if(dp[i-1]+A[i]>A[i]){
dp[i]=dp[i-1]+A[i];
pre[i]=i-1;
}
else{
dp[i]=A[i];
pre[i]=i;
}
}
int k=0;//保存最大连续子序列和的结尾下标
for(int i=1;i<n;i++){
if(dp[i]>dp[k]){
k=i;
}
}
int s=k;//s保存序列首元素下标
while(pre[s]!=s){
s=pre[s];
}
printf("%d %d %d",dp[k],A[s],A[k]);
return 0;
}
97. 最喜欢颜色序列(1045)
本题要求从序列中取出不喜欢的数字,并确定剩余序列中按喜欢子序列顺序选择的最大长度。
(1)去除不喜欢的元素
(2)可以用LIS方法:状态转移方程为dp[i]=max{1,dp[j]+1}(j<i),转移条件是A[i]=A[j],或A[i]在喜欢序列中位置比A[j]靠后,可以用一个辅助数组存储喜欢数字出现顺序。
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10010;
int order[210]={0};//存储喜欢序列的序号,>0也表示数字是喜欢序列中元素
int num[maxn],valid[maxn],dp[maxn];
int main(){
int n,m,l;
scanf("%d%d",&n,&m);
int color;
for(int i=1;i<=m;i++){
scanf("%d",&color);
order[color]=i;
}
scanf("%d",&l);
int numVaild=0;
for(int i=0;i<l;i++){
scanf("%d",&num[i]);
if(order[num[i]]!=0) {//是喜欢序列中的元素
valid[numVaild++]=num[i];
}
}
//确定最长不降子序列
int ans=-1;//记录最长序列长
for(int i=0;i<numVaild;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(order[valid[i]]>=order[valid[j]]){
//当i在喜欢序列中排在j后面或等于j时,更新长度
dp[i]=dp[j]+1;
}
}
ans=max(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
当然本题也可以用LCS做,不同的地方在于由于可以公共子序列可以重复,所以在A[i]=B[j]时,喜欢序列A不应该直接探查下一个,A[i]还可以继续比较,因此状态转移方程在A[i]=B[j]修改为:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+1
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10010;
int color[210];
int num[maxn];
int dp[210][maxn];
int main(){
int n,m,l;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d",&color[i]);
}
scanf("%d",&l);
for(int i=1;i<=l;i++){
scanf("%d",&num[i]);
}
//边界
for(int i=0;i<=m;i++){
dp[i][0]=0;
}
for(int i=0;i<=l;i++){
dp[0][i]=0;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=l;j++){
if(color[i]==num[j]){
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+1;
}
else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
//printf("%d ",dp[i][j]);
}
//printf("\n");
}
printf("%d",dp[m][l]);
return 0;
}
98. 最长对称子串(1040)
本题要求求出字符串中最长的对称串长度,可以将字符串对称,用LCS算法。
不同地方在于,本题要求的回文字串是要连续的,因此这里用LCS也要改为求最长相同连续子串,所以在A[i]!=B[j]时,dp[i][j]直接变为0。
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=1010;
char str[maxn],revStr[maxn];
int dp[maxn][maxn];
int main(){
scanf("%[^\n]",str);
int len=strlen(str);
for(int i=0;i<len;i++){
revStr[i]=str[len-i-1];
}
for(int i=0;i<=len;i++){
dp[i][0]=0;
dp[0][i]=0;
}
int maxlen=1;
for(int i=1;i<=len;i++){
for(int j=1;j<=len;j++){
if(str[i-1]==revStr[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
maxlen=max(maxlen,dp[i][j]);
}
else{
dp[i][j]=0;
}
}
}
printf("%d",maxlen);
return 0;
}
99. 找硬币(1068)
本题要求从给定序列中选择部分数字,使其和等于金额。
方法一:DFS递归求解,
先将硬币排序,每种硬币有选与不选两种情况,分别递归,当超过金额时返回,当等于和时,则需要与其它方案比较,选出更小的。
但是对于本题,有两个测试点会超时。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=10010;
int n,m;
int num[maxn];
vector<int> temp,ans;
void DFS(int sum,int index){//当前已选硬币总价值,当前待处理序号
if(sum>m||index>=n) return;
if(sum==m){
if(ans.size()==0){//第一次发现解
ans=temp;
}
return;
}
//选第index个硬币
temp.push_back(num[index]);
//printf("push:%d\n",num[index]);
DFS(sum+num[index],index+1);
//不选第index个硬币
temp.pop_back();
//printf("pop:%d\n",num[index]);
DFS(sum,index+1);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&num[i]);
}
sort(num,num+n);
DFS(0,0);
if(ans.size()==0){
printf("No Solution");
}
else{
for(int i=0;i<ans.size();i++){
printf("%d",ans[i]);
if(i<ans.size()-1) printf(" ");
}
}
return 0;
}
方法二:01背包问题方法
在这里,总金额m对应背包容量,物品的价值和重量则都对应硬币面额。
注意:(1)dp[m]!=m对应于无法在背包容量为m时,凑不出价值为m物品,即无解;
(2)多解时要求按字典序,所以可以先将硬币按大小排序,我们的目的是优先保存最小金额小的,但是dp是从右开始更新的,所以应该是从大到小排;
(3)还要求输出硬币组合情况,那么还需要一个辅助数组记录物品的存放情况。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=10010;
const int maxv=110;
int n,m;
int w[maxn],dp[maxv];
bool choice[maxn][maxv];//choice[i][v]表示dp[i][v]抉择时是否加入了第i个硬币
bool flag[maxv];//表示第i个硬币是否选择
vector<int> temp,ans;
bool cmp(int a,int b){
return a>b;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
}
sort(w+1,w+n+1,cmp);
//初始边界
for(int i=0;i<=m;i++){
dp[i]=0;
}
for(int i=1;i<=n;i++){
for(int v=m;v>=w[i];v--){
if(dp[v]<=dp[v-w[i]]+w[i]){
//放入第i件物品
/*等于时表明至少有两种方案,选择w[i]和不选w[i],在相等时仍然选择w[i]
是因为,越往后的w[i]越大,在总金额不变时,前面的金额就越小,从而保证选择字典序小的
*/
dp[v]=dp[v-w[i]]+w[i];
choice[i][v]=1;
}
else choice[i][v]=0;
}
}
if(dp[m]!=m){
printf("No Solution");
}
else{//查找选择硬币情况
//这个过程和LCS算法里从辅助矩阵中找出子序列过程类似
int k=n,num=0,v=m;
while(k>=0){
if(choice[k][v]==1){
flag[k]=true;
v-=w[k];
num++;
}
else flag[k]=false;
k--;
}
for(int i=n;i>=1;i--){
if(flag[i]){
printf("%d",w[i]);
num--;
if(num>0) printf(" ");
}
}
}
return 0;
}
100. 栈(1057)
本题要求给出任意状态下栈的中位数,如果栈为空,碰到Pop和查找中间元素作出Invalid判断。使用分块思想,把栈内元素划分成n^1/2的大小,用辅助数组存储每块的数量,每次插入后,只需确定插入块,修改块内元素大小。而查询时,也是先确定所在块,在根据之前各块大小,确定查询的中位数在所在块中的序号。
#include<cstdio>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=100010;
const int sqrN=316;//1000001开根号结果,为最大所需块数
stack<int> st;//实现初入栈操作
int block[sqrN]={0};//记录每块中元素个数
int table[maxn]={0};//hash表,用于表示x当前存在个数
int n;
void Push(int x){//入栈及其相应处理
st.push(x);
block[x/sqrN]++;//对应块元素个数+1
table[x]++;//对应元素个数+1
}
void Pop(){
if(st.empty()) {//栈为空
printf("Invalid\n");
return;
}
int x=st.top();
st.pop();
block[x/sqrN]--;//对应块元素个数-1
table[x]--;//对应元素个数-1
printf("%d\n",x);
}
void Median(){
if(st.empty()) {//栈为空
printf("Invalid\n");
return;
}
//确定中位数序号
int K=st.size();
if(K%2==0) K/=2;
else K=(K+1)/2;
int sum=0,index=0;//已累计元素个数,块号
//确定中位数所在块号
while(sum+block[index]<K){
sum+=block[index];
index++;
}
//在对应块内找到查找数字
int num=sqrN*index;//所在块第一个数字
while(sum+table[num]<K){
sum+=table[num];//累加块内数字个数
num++;
}
printf("%d\n",num);
}
int main(){
scanf("%d",&n);
char str[20];
int x;
for(int i=0;i<n;i++){
scanf("%s",str);
if(strcmp(str,"Push")==0){
scanf("%d",&x);
Push(x);
}
else if(strcmp(str,"Pop")==0){
Pop();
}
else if(strcmp(str,"PeekMedian")==0){
Median();
}
}
return 0;
}
101. 螺旋数组(1105)
本题要求把给定数组排成螺旋数组
(1)螺旋数组大小m和n如何确定:求出N与sqrt(N)最近的因子即为n;
(2)如何确定各元素的位置:螺旋数组可以看作按圈填入,每圈又分为右、下、左、上移填入四个步骤;
#include<cstdio>
#include<math.h>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=10010;
int m,n;
int A[maxn],matrix[maxn][maxn];
void m_n(int N){
int sqrN=(int)sqrt(1.0*N);
for(int i=sqrN;i>=1;i--){
if(N%i==0) {
n=i;
m=N/n;
return;
}
}
}
bool cmp(int a,int b){
return a>b;
}
int main(){
int N;
scanf("%d",&N);
for(int i=0;i<N;i++){
scanf("%d",&A[i]);
}
sort(A,A+N,cmp);
//确定m和n的大小
m_n(N);
//printf("%d %d",m,n);
int num=0,x=0,y=0;//num表示已填入螺旋数组个数,x,y为所在下标
int cycle=1;//当前圈数
//填入螺旋数组
if(N==1) {//1个元素特判
printf("%d",A[0]);
return 0;
}
while(num<N){
//左移
while(num<N&&x<n-cycle){
matrix[y][x] = A[num++];
x++;
}
//下移
while(num<N&&y<m-cycle){
matrix[y][x] = A[num++];
y++;
}
//右移
while(num<N&&x>=cycle){
matrix[y][x] = A[num++];
x--;
}
//上移
while(num<N&&y>=cycle){
matrix[y][x] = A[num++];
y--;
}
//填完回到起点,现在移动到内一圈的起点
x++;
y++;
//printf("x=%d y=%d,num=%d\n",x,y,num);
cycle++;
//对完全平方数,最中心的数需要特别处理,否则一直无法填入,陷入死循环
if(n==m&&num==N-1){
matrix[x][y]=A[num++];
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
printf("%d",matrix[i][j]);
if(j<n-1) printf(" ");
}
printf("\n");
}
return 0;
}
102. 银行排队(1017)
本题要求计算用户的平均等待时间。
等待时间=开始处理时间 - 抵达时间。所以当所有窗口的最早空闲时间>抵达时间时就需要等待。
(1)用结构体存储每个用户的抵达时间,处理时间,和等待时间(全部先化为秒)
(2)先按抵达时间顺序对用户排序,确定优先处理顺序
(3)设立一个窗口数组,存储该窗口的下一空闲时刻。同时每处理一个用户,要随时更新最早窗口空闲时间。
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
const int maxn=10010;
const int maxk=110;
const int openTime=8*3600,closeTime=17*3600;
int windows[maxk];//存储窗口下一空闲时间
int n,k;
struct Customer{
int arriveTime;
int ProcessTime;
//int waitTime=0;
}cust[maxn];
bool cmp(Customer x,Customer y){
return x.arriveTime<y.arriveTime;
}
int findMinTime(){
int idx=0;
for(int i=1;i<k;i++){
if(windows[idx]>windows[i]) {
idx=i;
}
}
return idx;
}
int main(){
scanf("%d%d",&n,&k);
int hh,mm,ss;
for(int i=0;i<n;i++){
scanf("%d:%d:%d %d",&hh,&mm,&ss,&cust[i].ProcessTime);
cust[i].arriveTime=hh*3600+mm*60+ss;
cust[i].ProcessTime*=60;
}
//按抵达时间排序
sort(cust,cust+n,cmp);
int sum=0,numValid=0;//总等待时间,有效客人数
int idx=0;//当前窗口最早空闲时间,idx存储最早空闲时间下标
fill(windows,windows+k,openTime);//初始化所有窗口空闲时间都是开门时间
for(int i=0;i<n;i++){
if(cust[i].arriveTime>closeTime){//17点后来的
break;
}
numValid++;
if(cust[i].arriveTime<windows[idx]){//需要等待
sum += windows[idx] - cust[i].arriveTime;
//printf("sum=%d\n",sum);
//更新等待时间
windows[idx] += cust[i].ProcessTime;
idx=findMinTime();
}
else{//不需等待的情况
windows[idx] = cust[i].arriveTime+cust[i].ProcessTime;
idx=findMinTime();
}
}
double avgWait =(1.0*sum)/(60*numValid);
printf("%.1f",avgWait);
return 0;
}
103. 银行排队(1014)
本题要求根据排队情况,确定每个用户是否能够办理业务
(1)设立K个队列模拟排队情况,
(2)先把队列全部填满,之后按每个队列最快出队时间来推进时间前进,此时空出空位
下一个客户进队;之后更新最快出队时间,直到时间到达5:00,或者所用用户都已出队。
测试点4卡了很久,发现可能是下面这个例子情况:
2 2 4 4
10 540 540 2
1 2 3 4
正确答案应该是:
08:10
17:00
17:10
Sorry
如果不在39添加LineTime[i]<close 的判断条件,那么已经不能服务的队列2会干扰选择出当前最快完成队列的选择(因为502<510),就会以为所有队列都超过17.00了可以break,但是实际上第一个队列还有一个540分钟的还没办理,即使他要办理到17:10,也必须给他办理完成。
#include<cstdio>
#include<queue>
using namespace std;
const int maxk=1010;
const int open=8*60;
const int close=17*60;
queue<int> wait[25];//窗口队列
int LineTime[25];//存储各窗口当前的时间
int Process[maxk],Finish[maxk];
int main(){
int n,m,k,q;
scanf("%d%d%d%d",&n,&m,&k,&q);
for(int i=1;i<=k;i++){
scanf("%d",&Process[i]);
}
//先把队列填满,作为初始排队情况
for(int i=0;i<n;i++){
LineTime[i]=open;
}
int num=1;
while(num<=m*n&&num<=k){
for(int i=0;i<n;i++){
//把每个用户序号存入队列
wait[i].push(num++);
if(num>k) break;
}
}
int time=open,numValid=0;//时间和办理完成业务人数
int index=0,id;//空出的队列序号,客户序号
while(numValid<=num){//当前队列还有客户没办理业务
//找出各队列中最早出现空闲的队列
int minTime=24*60;
for(int i=0;i<n;i++){
if(!wait[i].empty()&&LineTime[i]<close){
//第二个条件是避免已经不能办理业务的队列,对还有剩余元素,且它的办理时间将会超过5点的队列进行干扰
id=wait[i].front();
int outLine=LineTime[i]+Process[id];//各个队列当前队首离队时间
//printf("id=%d,出队时间=%d\n",id,outLine);
if(minTime>outLine){
minTime=outLine;
index=i;
}
}
}
if(LineTime[index]>=close){//当前所有队列时间过了17点,不再接受办理
break;
}
//printf("LineTime[index]=%d\n",LineTime[index]);
id=wait[index].front();
LineTime[index]+=Process[id];//更改改队列的时间
wait[index].pop();
Finish[id]=LineTime[index];
//printf("id=%d,Finish=%d\n",id,Finish[id]);
numValid++;//有效办理业务客户数加一
time=LineTime[index];
if(time>=close){//若此时时间过了5点就不再让人入队,但队列里可能还有没处理完
continue;
}
else if(time<=close&&num<=k) {//当前还有人没进队,且还没过5点,则令下一位进队
wait[index].push(num++);
//printf("time=%d,%d入队\n",time,num-1);
}
}
//printf("numValid=%d\n",numValid);
int query;
for(int i=0;i<q;i++){
scanf("%d",&query);
//不能根据序号与有效办理业务的人数大小关系输出,因为序号大的人可能在别的队列成功办理了
if(Finish[query]>0) printf("%02d:%02d\n",Finish[query]/60,Finish[query]%60);
else{
printf("Sorry\n");
}
}
return 0;
}