一个很显然的根据前缀划分的贪心,直接指针模拟就好了。
| #include<bits/stdc++.h> |
| using namespace std; |
| int main() |
| { |
| int t; |
| cin>>t; |
| while(t--) |
| { |
| string a,b; |
| cin>>a>>b; |
| int l1=a.length(),l2=b.length(); |
| int p=0; |
| while(p<l1&&p<l2&&a[p]==b[p])p++; |
| if(p>0)p--; |
| cout<<l1+l2-p<<'\n'; |
| } |
| |
| } |
打表就发现需要输出的就是 ,然而这道题我卡了十多分钟,实在是有点sb了。
具体证明的话可以用数学归纳法,也可以直接证明,都是比较容易的。
| #include<bits/stdc++.h> |
| #define int long long |
| using namespace std; |
| const int MOD=1e9+7; |
| inline int power(int a,int b) |
| { |
| int ans=1; |
| while(b) |
| { |
| if(b&1)ans=ans*a%MOD; |
| a=a*a%MOD; |
| b>>=1; |
| } |
| return ans%MOD; |
| } |
| const int N=2e5+1; |
| int t,n[N],k[N]; |
| inline void solve() |
| { |
| cin>>t; |
| for(int i=1;i<=t;++i)scanf("%d",&n[i]); |
| for(int i=1;i<=t;++i)scanf("%d",&k[i]),printf("%lld\n",power(2,k[i])); |
| } |
| signed main() |
| { |
| solve(); |
| } |
其实思路上面很简单,只需要以每个数为开头贪心地连续去选择尽可能多的数字就可以了。
注意这样做是 的,那么考虑如何在枚举的时候快速地从现在的答案推知下一个答案。
很容易想到一个简化的莫队,从 到 ,实际上只是增加了一个数,去除了一个数而已,通过增量就可以计算出来。
边界写错了吃了两发罚时(实际上可以不需要去重的,所以说本人的找屎能力真的强)。
又成功地在模拟题上面写出了一道屎山代码。。。
| #include<bits/stdc++.h> |
| #define int long long |
| using namespace std; |
| template<typename T>inline void re(T &x) |
| { |
| x=0;int f=1;char c=getchar(); |
| while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} |
| while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} |
| x*=f; |
| } |
| template<typename T>inline void wr(T x) |
| { |
| if(x>9)wr(x/10); |
| putchar(x%10^48); |
| } |
| int t,n,k,a[200010]; |
| pair<int,int>b[200010]; |
| inline void solve() |
| { |
| re(n),re(k); |
| for(register int i=1;i<=n;++i)re(a[i]); |
| sort(a+1,a+n+1);a[n+1]=0; |
| int cnt=1,p=0; |
| for(register int i=1;i<=n;++i) |
| { |
| if(a[i]!=a[i+1]) |
| { |
| b[++p].first=a[i]; |
| b[p].second=cnt; |
| cnt=1; |
| } |
| else cnt++; |
| } |
| int ans=-1,lasl=0,lasr=0; |
| int lasans=0; |
| for(register int i=1;i<=p;++i) |
| { |
| if(!lasans) |
| { |
| int p1=i+1;lasans=b[i].second; |
| while(p1<=p&&p1-i+1<=k&&b[p1].first-b[p1-1].first==1)lasans+=b[p1++].second; |
| ans=max(ans,lasans),lasl=i,lasr=p1-1; |
| } |
| else |
| { |
| int nowr=lasr+i-lasl; |
| if(b[nowr].first-b[nowr-1].first!=1||nowr>p) |
| { |
| i=nowr-1; |
| lasans=lasl=lasr=0; |
| continue; |
| } |
| lasans-=b[i-1].second,lasans+=b[nowr].second; |
| ans=max(ans,lasans); |
| } |
| } |
| wr(ans),putchar('\n'); |
| } |
| signed main() |
| { |
| re(t); |
| while(t--) |
| solve(); |
| return 0; |
| } |
因为实现能力太差,C做了一个小时,导致D只有不到二十分钟的时间了,大概扫过去想了一个很骚的做法,就是倒叙枚举然后贪心地选择贡献即可,结果发现题看错了。。。
这种涉及到抉择的题发现贪心是假的,那肯定就是DP了。
那其实阶段和状态也很容易设计出来。
定义 为前 个数字 选择了 点智力加点能够获得的分数(注意此时体力的加点一定是唯一确定的,因此我们没有必要单独开一维来单独记录,前提是我们需要统计出来一个数组来记录任意某个位置 之前一共出现了多少个 ) 。
那么转移方程也非常的水到渠成:
如果 ,那么所有的
如果 ,那么所有的
如果 ,那么所有的
注意到我们会多次执行区间加的操作,实际上你可以写一个线段树,或者是树状数组,然而都太麻烦或者不好实现(对我来说)。
我们可以直接采用差分数组,但是缺陷就在于,虽然可以多次进行区间加法,但是一只能统计一次。观察到这道题 的数据范围非常小,所以每次遇到一个 之前,我们就暴力地统计一遍差分数组,然后加到 数组里面,而后进行 处该进行的转移,再把差分数组清零,重新进行后续的区间加操作即可。
| #include<bits/stdc++.h> |
| using namespace std; |
| template<typename T>inline void re(T &x) |
| { |
| x=0;int f=1;char c=getchar(); |
| while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} |
| while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} |
| x*=f; |
| } |
| template<typename T>inline void wr(T x) |
| { |
| if(x>9)wr(x/10); |
| putchar(x%10^48); |
| } |
| const int N=2e6+100; |
| int a[N],dp[5010]; |
| int n,m; |
| int s[5010]; |
| inline void add(int l,int r,int v) |
| { |
| s[l]+=v,s[r+1]-=v; |
| } |
| inline void calcsum() |
| { |
| for(int i=1;i<=m;++i) |
| s[i]+=s[i-1]; |
| for(int i=0;i<=m;++i) |
| dp[i]+=s[i],s[i]=0; |
| } |
| inline void pre() |
| { |
| re(n),re(m); |
| for(register int i=1;i<=n;++i)re(a[i]); |
| } |
| inline void solve() |
| { |
| int sum0=0; |
| for(register int i=1;i<=n;++i) |
| { |
| if(a[i]==0) |
| { |
| calcsum(); |
| for(register int j=++sum0;j>=1;--j) |
| dp[j]=max(dp[j],dp[j-1]); |
| } |
| else if(a[i]>0) |
| { |
| if(sum0<a[i])continue; |
| add(a[i],sum0,1); |
| } |
| else |
| { |
| if(sum0<abs(a[i]))continue; |
| add(0,sum0+a[i],1); |
| } |
| } |
| calcsum(); |
| int ans=-1; |
| for(register int i=0;i<=m;++i)ans=max(ans,dp[i]); |
| wr(ans); |
| } |
| int main() |
| { |
| pre(); |
| solve(); |
| return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
非常好的一道计数题,让我“温习“了一下在高二的时候学习过的卡特兰数。
卡特兰数
由于这一类组合数对于实际问题有意义,所以才会被单独讨论,它适用于如下情况:
- 有 个人排成一行进入剧场。入场费 5 元。其中只有个 人有一张 5 元钞票,另外人 只有 10 元钞票,剧院无其它钞票,问有多少种方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零?
- 有一个大小为 的方格图开始每次都只能向右或者向上走一单位,不走到对角线上方(但可以触碰)的情况下到达右上角有多少可能的路径?
- 在圆上选择 个点,将这些点成对连接起来使得所得到的条 线段不相交的方法数?
- 对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方法数?
- 一个栈(无穷大)的进栈序列为 有多少个不同的出栈序列?
- 个结点可构造多少个不同的二叉树?
- 由 个 组成的序列,满足任意前缀和非负,有多少种构造方案?
-
-
-
-
DP的方式求(卡特兰数实际上是一个匹配的过程)
定义 为,前 个数,剩下了 个没有匹配的方案数,那么最后的答案就是
转移方程也很好写,
当前作为左括号。
当前作为右括号并和一个左括号匹配。
我们钦定两个人分别为 ,明显的是这道题中 是一个比较特殊的花色,对于其他普通的花色,我可以两个人选的一样,然后两个人内部进行分配使得 总是赢;也可以暂时让 的牌多于 ,后面拿若干张的 来进行弥补性配对。
我们考虑如何进行这个计数DP。
对于 花色特殊考虑,我们先不处理它。
对于其他任意一种颜色,我们应用第一种分配策略时,把 张牌均分给两个人,首先假定这个 的序列已经降序排列好了。
那么我们从大到小以此给两人分配,发现如果任何时刻,我给 分配的牌都必须要小于等于给 分配的牌,才能够满足题设条件。
那么这实际上就是一个卡特兰数 ,可以比较性地发现。
我们应用第二种分配时,很容易想到先选出 (偶数)个来不进行配对之后和 配,然后剩下的 个按照卡特兰数配对。
看起来没什么问题,实际上也确实没问题,但在这道题的计数规则下就是错误的,由于我选出来不配对的几个数实际上是归于 的,这就意味着分别属于 的参与匹配的集合 和 A的暂时不参与匹配集合 的两个数,可能会被重复计算。
反观我们用 DP 求卡特兰数的过程,实际上就已经把这里我们需要的答案给计算出来了,我们想要剔除 个,然后剩下的配对,那么方案数就是上面提到的 ,那么我们转移起来也就相对来说很好想了。
的定义是 前 个花色 (不包括 ),一共剩下了 个的方案数。
| #include<bits/stdc++.h> |
| #define int long long |
| using namespace std; |
| const int MOD=998244353,N=600; |
| int dp[N][N],n,m; |
| inline int power(int a,int b) |
| { |
| int ans=1; |
| while(b) |
| { |
| if(b&1)ans=ans*a%MOD; |
| a=a*a%MOD; |
| b>>=1; |
| } |
| return ans%MOD; |
| } |
| inline void garbage_bin() |
| { |
| int C[N][N],catalan[N]; |
| for(register int i=0;i<=m;++i) |
| { |
| C[i][0]=1; |
| for(register int j=1;j<=i;++j) |
| C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD; |
| } |
| catalan[0]=1; |
| for(register int i=1;i<=m;i++) |
| catalan[i]=catalan[i-1]*(4*i-2)%MOD*power(i+1,MOD-2)%MOD; |
| } |
| int cat[N][N]; |
| void pre() |
| { |
| cin>>n>>m; |
| cat[0][0]=1; |
| for(register int i=1;i<=m;++i) |
| for(register int j=0;j<=m;++j) |
| { |
| if(j-1>=0)cat[i][j]+=cat[i-1][j-1],cat[i][j]%=MOD; |
| cat[i][j]+=cat[i-1][j+1],cat[i][j]%=MOD; |
| } |
| } |
| void solve() |
| { |
| dp[0][0]=1; |
| for(register int i=1;i<=n-1;++i) |
| { |
| for(register int j=0;j<=m;j+=2) |
| for(register int k=0;k<=j;k+=2) |
| dp[i][j]+=dp[i-1][j-k]*cat[m][k]%MOD,dp[i][j]%=MOD; |
| } |
| |
| int ans=0; |
| for(register int j=0;j<=m;j+=2) |
| ans+=cat[m][j]*dp[n-1][j]%MOD,ans%=MOD; |
| cout<<ans; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| signed main() |
| { |
| pre(); |
| solve(); |
| return 0; |
| } |
好久没有写过题解了。。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效