前面的碎碎念:
本次比赛A了A/D两题,B题拿优先队列BFS瞎搞了半天,发现状态数太多无法开出标记数组,遂扑街。C题思考了一会,发现有点差分数组那味,但是没有想出啥解法,遂扑街。赛后补完题,感觉B题没有做出来有点不应该,优先队列做法其实很像BFS搜索的过程,DP做法也很妙,思维上还是差一点。C题有点接近了,但是最关键的部分并没有想出来,不过比赛中A了这题的人并不多,情有可原。
题解部分:
A-咪咪游戏
签到题。字符串若全是由mq链接而成,这个字符串长度肯定是偶数,所以长度为奇数的字符串肯定非法。若字符串长度为偶数,我们就遍历一遍,看其是否均由mq连接而成即可。
时间复杂度:O(Q|s|)
代码部分:
#include<bits/stdc++.h> using namespace std; char R[100005]; int main() { int i,n,Q; bool flag; scanf("%d",&Q); while(Q--) { scanf("%s",R); n=strlen(R),flag=1; if(n&1)flag=0; for(i=0;flag&&i<n;i+=2) { if(R[i]=='m'&&R[i+1]=='q')continue; else flag=0; } if(flag)printf("Yes\n"); else printf("No\n"); } return 0; }
B-三角形
此题既可以使用优先队列求解,也可以使用DP求解。
解法一:优先队列
首先对每行都从小到大排序,之后我们开个B数组,用以保存最终的前k小值。先把第一行元素全部赋给B数组,然后开始逐行更新B数组:
对于第i行,我们把B[1]+A[i][1],B[2]+A[i][1],...,B[k]+A[i][1]全部压入优先队列。之后开始出队,出队更新B数组的同时,还要把其对应的A[i][j]替换成A[i][j+1]再次压入队。什么意思呢,比如出队元素的值对应为原来的B[1]+A[i][1],那么我们就要把B[1]+A[i][2]压入队,也就是把B[1]+A[i][1]-A[i][1]+A[i][2]压入队。通过这样的操作,我们就可以得到一个新的B数组,其保存了从第一行到该行的前k小值,这部分的做法其实有点类似于BFS搜索。
经过逐行更新后,我们最后再遍历一遍B数组,把前小值全部加起来输出即可。
时间复杂度:O(nklog(k))
代码部分:
#include<bits/stdc++.h> using namespace std; struct node { int w,h; bool operator<(const node &a)const { return a.w<w; } }r,f; int A[105][105],B[10005]; priority_queue<node>Q; int main() { int i,j,k,n,m,ans=0; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&A[i][0]); for(j=1;j<=A[i][0];j++)scanf("%d",&A[i][j]); sort(A[i]+1,A[i]+1+A[i][0]); } for(i=1;i<=A[1][0];i++)B[i]=A[1][i]; B[0]=A[1][0]; for(i=2;i<=n;i++) { while(!Q.empty())Q.pop(); for(j=1;j<=B[0];j++)r.w=B[j]+A[i][1],r.h=1,Q.push(r); for(j=1;j<=min(m,B[0]*A[i][0]);j++) { f=Q.top(),Q.pop(); B[j]=f.w; if(f.h+1<=A[i][0])r.w=f.w-A[i][f.h]+A[i][f.h+1],r.h=f.h+1,Q.push(r); } B[0]=j-1; } for(i=1;i<=m;i++)ans+=B[i]; printf("%d\n",ans); }
解法二:DP
我们设DP[i][j]为前i行每行选一个元素,其和为j的方案数。设原数组为A,前n行每行选一个数的和的最大值为s,那么有状态转移方程:
for(DP[0][0]=i=1;i<=n;i++) for(j=1;j<=A[i][0];j++) for(k=s;k>=A[i][j];k--)DP[i][k]+=DP[i-1][k-A[i][j]]
求解出这个DP数组后,我们就知道了前n行每行选一个元素,其和为1,2,3,...,10000的方案数。我们按和从小到大遍历一边DP数组,根据数量进行加减即可得出最终答案。
时间复杂度:O(n^2mmax(wi))
代码部分:
#include<bits/stdc++.h> using namespace std; int A[105][105],DP[105][10005]; int main() { int i,j,k,n,m,s=0,ans=0; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&A[i][0]),k=0; for(j=1;j<=A[i][0];j++)scanf("%d",&A[i][j]),k=max(k,A[i][j]); s+=k; } for(DP[0][0]=i=1;i<=n;i++) for(j=1;j<=A[i][0];j++) for(k=s;k>=A[i][j];k--)DP[i][k]+=DP[i-1][k-A[i][j]]; for(j=0,i=1;j<m&&i<=s;i++) { if(m-j>=DP[n][i])j+=DP[n][i],ans+=i*DP[n][i]; else ans+=(m-j)*i,j=m; } printf("%d\n",ans); return 0; }
C-区间加
此题有位大佬写的很妙,这里借鉴他的做法,考虑从差分数组的角度理解。
我们用A数组保存每个位置离变成m,还需要加多少次1,用j记录下现在还没有匹配的区间左端点的个数。
那么从左往右扫一遍A数组,令ans初值为1,有以下四种情况:
一:若A[i]-A[i+1]的绝对值大于1,那么就表示非法,直接输出0结束程序。因为每个点只能作为最多一个区间的端点,这样假设相邻的两个点i,i+1。i左边未配对的左端点数量为a,如果i处放一个右端点,那么包含i的区间数量就是a,包含i+1的就是a(在i+1处放一个左端点)或者a-1(i+1处不放左端点)。如果i处放一个左端点,那么包含i的区间数量为a+1,包含i+1的为a+1(i+1处放一个右端点或不放)或a+2(i+1处放一个左端点)。所以所有情况都满足i和i+1的差值不超过1。
二:若A[i]-A[i+1]=1,那么我们就要在i这个位置放一个右端点,对应的左端点有j种选择,所以答案乘以j。同时因为左端点已经匹配了一个,因此j要减一。
三:若A[i]-A[i+1]=0,我们可以在i处放一个右端点,再在i+1处放一个左端点,放右端点的时候有j种方案。当然可以什么都不做,所以就有(j+1)种方案,所以答案乘以(j+1)即可。
四:若A[i]-A[i+1]=-1,我们就要在i+1这个位置放一个左端点,因此j要加一。
时间复杂度:O(n)。
代码部分:
#include<bits/stdc++.h> using namespace std; const long long mod=998244355; int main() { int i,j,n,m,A[2005]={0}; long long ans=1; scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&j),A[i]=m-j; for(i=j=0;i<=n;i++) { if(abs(A[i]-A[i+1])>1) { printf("0\n"); return 0; } if(A[i]-A[i+1]==1)ans=ans*j%mod,j--; else if(A[i]==A[i+1])ans=ans*(j+1)%mod; else j++; } printf("%lld\n",ans); return 0; }
D-多元组
我们先考虑DP解法,设状态DP[i][j][k]为:前i个元素中,长度为j,且最后一个元素值为k的严格上升序列的个数,那么DP[n][m][0]+DP[n][m][1]+...+DP[n][m][max(a)]即为最终答案,状态转移方程如下:
for(i=1;i<=n;i++) { for(j=1;j<=m;j++) for(k=0;k<=max(a);k++)DP[i][j][k]=DP[i-1][j][k]; DP[i][1][a[i]]++; for(j=2;j<=m;j++) for(k=0;k<a[i];k++)DP[i][j][a[i]]+=DP[i-1][j-1][k]; }
其中DP数组第一维可以利用滚动数组优化,而第三维可以利用离散化+二分查找优化,但即使如此,此DP做法的时间复杂度也高达O(mn^2log(n))。
观察状态转移方程,我们不难发现这个状态转移过程可以由树状数组优化。设DP[i][j]为长度为i,且最后一位为j的严格上升序列的个数。那么状态转移过程就变成了:
for(i=1;i<=n;i++) { update(1,a[i],1); for(j=2;j<=m;j++)update(j,a[i],getsum(j-1,a[i]-1)); }
那么最终答案即是getsum(m,max(a[i]))。当然同样的,我们要先对a数组进行离散化处理,不然a[i]太大会导致树状数组开不出来。
时间复杂度:O(nmlog(n))
代码部分:
#include<bits/stdc++.h> using namespace std; const int mod=1e9+7; int L,A[100005],B[100005],DP[55][100005]; int lowbit(int x) { return x&(-x); } void update(int j,int i,int x) { while(i<L)DP[j][i]=(DP[j][i]+x)%mod,i+=lowbit(i); } int getsum(int j,int i) { int s=0; while(i)s=(s+DP[j][i])%mod,i-=lowbit(i); return s; } int main() { int i,j,k,n,m; scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&A[i]),B[i]=A[i]; sort(B+1,B+1+n); for(i=L=2;i<=n;i++)if(B[i]!=B[i-1])B[L++]=B[i]; for(i=1;i<=n;i++) { k=lower_bound(B+1,B+L,A[i])-B; update(1,k,1); for(j=2;j<=m;j++)update(j,k,getsum(j-1,k-1)); } printf("%d\n",getsum(m,L-1)); return 0; }
完结撒花,若有疏漏之处,请大佬们轻喷。