Edu Round 170 Review
A
分析
一个很显然的根据前缀划分的贪心,直接指针模拟就好了。
Code
#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';
}
}
B
分析
打表就发现需要输出的就是 \(2^n\) ,然而这道题我卡了十多分钟,实在是有点sb了。
具体证明的话可以用数学归纳法,也可以直接证明,都是比较容易的。
Code
#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();
}
C
分析
其实思路上面很简单,只需要以每个数为开头贪心地连续去选择尽可能多的数字就可以了。
注意这样做是 \(O(n^2)\) 的,那么考虑如何在枚举的时候快速地从现在的答案推知下一个答案。
很容易想到一个简化的莫队,从 \([l,r]\) 到 \([l+1,r+1]\) ,实际上只是增加了一个数,去除了一个数而已,通过增量就可以计算出来。
边界写错了吃了两发罚时(实际上可以不需要去重的,所以说本人的找屎能力真的强)。
又成功地在模拟题上面写出了一道屎山代码。。。
Code
#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;
}
D
分析
因为实现能力太差,C做了一个小时,导致D只有不到二十分钟的时间了,大概扫过去想了一个很骚的做法,就是倒叙枚举然后贪心地选择贡献即可,结果发现题看错了。。。
这种涉及到抉择的题发现贪心是假的,那肯定就是DP了。
那其实阶段和状态也很容易设计出来。
定义 \(dp[i][j]\) 为前 \(i\) 个数字 选择了 \(j\) 点智力加点能够获得的分数(注意此时体力的加点一定是唯一确定的,因此我们没有必要单独开一维来单独记录,前提是我们需要统计出来一个数组来记录任意某个位置 \(pos\) 之前一共出现了多少个 \(0\) ) 。
那么转移方程也非常的水到渠成:
如果 \(a_i=0\) ,那么所有的 \(dp[i][j]=\max(dp[i-1][j],dp[i-1][j-1])\)
如果 \(a_i>0\) ,那么所有的 \(dp[i][j](j\ge a_i)=dp[i-1][j]+1\)
如果 \(a_i<0\) ,那么所有的 \(dp[i][j](j\le sum0-(-a_i))=dp[i-1][j]+1\)
注意到我们会多次执行区间加的操作,实际上你可以写一个线段树,或者是树状数组,然而都太麻烦或者不好实现(对我来说)。
我们可以直接采用差分数组,但是缺陷就在于,虽然可以多次进行区间加法,但是一只能统计一次。观察到这道题 \(m\) 的数据范围非常小,所以每次遇到一个 \(0\) 之前,我们就暴力地统计一遍差分数组,然后加到 \(dp\) 数组里面,而后进行 \(0\) 处该进行的转移,再把差分数组清零,重新进行后续的区间加操作即可。
Code
#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;
}
/*
dp[i][j] 前i个数字 拥有 j 点智力可以获得的分数
all dp[i][j]=dp[i-1][j];
if(0)
dp[i][j]=max dp[i][j],dp[i-1][j-1];
if(+)
dp[i][j]=add(a[i],sum0[i],1);
if(-)
dp[i][j]=add(0,sum0[i]+a[i],1);
写的时候的问题:
前缀和之后没有给dp 0加上去
统计差分 和 进行 O(m) 转移的顺序搞反了
*/
E
非常好的一道计数题,让我“温习“了一下在高二的时候学习过的卡特兰数。
前置知识
卡特兰数
定义背景
由于这一类组合数对于实际问题有意义,所以才会被单独讨论,它适用于如下情况:
- 有 \(n\) 个人排成一行进入剧场。入场费 5 元。其中只有个 \(n\) 人有一张 5 元钞票,另外人 \(n\) 只有 10 元钞票,剧院无其它钞票,问有多少种方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零?
- 有一个大小为 \(n\times n\) 的方格图开始每次都只能向右或者向上走一单位,不走到对角线上方(但可以触碰)的情况下到达右上角有多少可能的路径?
- 在圆上选择 \(2n\) 个点,将这些点成对连接起来使得所得到的条 \(n\) 线段不相交的方法数?
- 对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方法数?
- 一个栈(无穷大)的进栈序列为 \(1-n\) 有多少个不同的出栈序列?
- \(n\) 个结点可构造多少个不同的二叉树?
- 由 \(n\) 个\(+-1\) 组成的序列,满足任意前缀和非负,有多少种构造方案?
公式
-
\(H_n=\frac{C_{2n}^{n}}{n+1}\)
-
\(H_n=C_{2n}^n-C_{2n}{n-1}\)
-
\(H_n=H_{n-1}\times \frac{4n-2}{n+1}\)
-
DP的方式求(卡特兰数实际上是一个匹配的过程)
定义 \(f_{i,j}\) 为,前 \(i\) 个数,剩下了 \(j\) 个没有匹配的方案数,那么最后的答案就是 \(f_{2n,0}\)
转移方程也很好写,
$$f_{i,j}+=f_{i-1,j-1}$$ 当前作为左括号。
$$f_{i,j}+=f_{i-1,j+1}$$ 当前作为右括号并和一个左括号匹配。
分析
我们钦定两个人分别为 \(A,B\) ,明显的是这道题中 \(1\) 是一个比较特殊的花色,对于其他普通的花色,我可以两个人选的一样,然后两个人内部进行分配使得 \(A\) 总是赢;也可以暂时让 \(B\) 的牌多于 \(A\) ,后面拿若干张的 \(1\) 来进行弥补性配对。
我们考虑如何进行这个计数DP。
对于 \(1\) 花色特殊考虑,我们先不处理它。
对于其他任意一种颜色,我们应用第一种分配策略时,把 \(m\) 张牌均分给两个人,首先假定这个 \(1-m\) 的序列已经降序排列好了。
那么我们从大到小以此给两人分配,发现如果任何时刻,我给 \(B\) 分配的牌都必须要小于等于给 \(A\) 分配的牌,才能够满足题设条件。
那么这实际上就是一个卡特兰数 \(H_{\frac{m}{2}}\),可以比较性地发现。
我们应用第二种分配时,很容易想到先选出 \(k\) (偶数)个来不进行配对之后和 \(1\) 配,然后剩下的 \(m-k\) 个按照卡特兰数配对。
看起来没什么问题,实际上也确实没问题,但在这道题的计数规则下就是错误的,由于我选出来不配对的几个数实际上是归于 \(A\) 的,这就意味着分别属于 \(A\) 的参与匹配的集合 和 A的暂时不参与匹配集合 的两个数,可能会被重复计算。
反观我们用 DP 求卡特兰数的过程,实际上就已经把这里我们需要的答案给计算出来了,我们想要剔除 \(k\) 个,然后剩下的配对,那么方案数就是上面提到的 \(f_{i,k}\) ,那么我们转移起来也就相对来说很好想了。
\(dp_{i,j}\) 的定义是 前 \(i\) 个花色 (不包括 \(1\) ),一共剩下了 \(j\) 个的方案数。
#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;
}
// 4 3 2 1
// 12A + 4A3B
// 13A + 4A2B
// 14A + 3A2B
// 23A + 4A1B
// 24A + 3A1B
// 34A + 2A1B
//dp 4 2=dp 3 1 (2)+dp3 3 (1)
signed main()
{
pre();
solve();
return 0;
}
好久没有写过题解了。。。。
标签:int,Review,Round,while,ans,MOD,void,dp,170 From: https://www.cnblogs.com/Hanggoash/p/18474771