CodeForce 1550
A
考虑最少,我们可以将序列数字设为:
\[1,3,5,7,9.... \]进行前缀和计算,找到 \(S\) 匹配到的区间值,其最少的数的个数即为对应大前缀和的下标。(这是一种时间复杂度更优的算法)
#include<bits/stdc++.h>
using namespace std;
int T,n;
int main(){
cin>>T;
while(T--){
cin>>n; int a=1,ans=0;
while(n>0) n-=a,ans++,a+=2;
cout<<ans<<endl;
}
return 0;
}
B
可分析出:\(a\) 的大小对答案无影响,\(b\) 的正负有影响。
当 \(b>0\) 我们希望其多加几个,最多可以添加 \(n\) 个 \(b\) ,答案就是 \(n*(a+b)\)
当 \(b<0\) 我们希望少加几个,尽量相同字符直接删除,此时我们计算字符不同的段数 \(cnt\)。
有两种不同序列形式:
- 前后数字相同,我们就先删中间不同数字的段落,然后整体删除相同数字段落,次数为 \(cnt/2+1\)
- 前后数字不同,我们就先删完一个数字,再整体删另一个数字,结果相同,次数为 \(cnt/2+1\)
因此便有以下代码:
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,a,b,T;
char s[N];
int main(){
cin>>T;
while(T--){
scanf("%d%d%d",&n,&a,&b); scanf("%s",s+1);
if(b>=0) printf("%d\n",n*(a+b));
else{
int cnt=0;
for(int i=1;i<=n;i++) if(s[i]!=s[i-1]) cnt++;
printf("%d\n",n*a+b*(cnt/2+1));//所以cnt/2的向下取整便是较小的连续为1或连续为0串
}
}
return 0;
}
C
推导式子:对于题目中的限制条件,我们可以转化成下面两种情况:
设符合条件 \(p>q>r\) ,则:
- \(a_p>a_q>a_r\)
- \(a_p<a_q<a_r\)
也就是单增和单减这两种情况是坏的。
序列需要连续,因此我们又可以分类讨论:
- \(len=1\) 有 \(n\) 个好序列
- \(len=2\) 有 \(n-1\) 个好序列
- \(len=3\) 我们需要不满足单增或单减,因此进行判断即可。
- \(len=4\) 枚举4种不同情况的排列方式,进行同上的判断即可
- 长度大于4,因为序列的数字都不一样,选取三个数字,必有单增或单减情况,因此不可能成立。
所以我们便有以下代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,a[N];
bool check(int x,int y,int z){
if((x<=y&&y<=z)||(x>=y&&y>=z)) return 0;
else return 1;
}
int main(){
cin>>T;
while(T--){
scanf("%d",&n); int ans=2*n-1;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n-2;i++) if(check(a[i],a[i+1],a[i+2])) ans++;
for(int i=1;i<=n-3;i++){
if(check(a[i],a[i+1],a[i+2])&&check(a[i],a[i+1],a[i+3])&&check(a[i],a[i+2],a[i+3])&&check(a[i+1],a[i+2],a[i+3]))
ans++;
}
printf("%d\n",ans);
}
// system("pause");
return 0;
}
D:
考虑数学知识+图像结合
如果直接做会发现题目条件有点多,考虑对题目化简
注意到 \(a_i+a_j=i+j\) 这个式子有很强的对称性,一般可以移项以后构造出一些等式
此题可以令 \(a_i=i+k_i\) 那么 \(a_i+a_j=i+j\) 即可转化为 \(k_i+k_j=0\) 即\(k_i=k_j\),就会有一对满足条件的数对
那么相当于对每个 \(a_i\) 赋值一个 \(k_i\) ,对于相同的数对我们希望它尽量多。画到坐标系里就是画两条分别代表 \(a_i=i+k_i\) 和 \(a_i=i−k_i\)
这里取相反数就是对应前面的"条件",每个 \(k_i\) 都对应了一种答案的取法
注意题目给的 \(l,r\) 范围很有意思,相当于在坐标系上画两个平行x轴的线来限定k ,限定等价于端点处的两个不等式 \(1−k≥l,n+k≤r\) 即有 \(k≥min(1−l,r−n)\)
图上可以显示得看出来,在这个条件下任意 \(a_i\) 都可以赋 \(+k_i\) 或者 \(−k_i\) (因为两个都满足了) 因此肯定是贪心的让数量均匀(基本不等式)
即贡献 \(lim=min(1−l,r−n)\) ,偶数时 \(lim \times \tbinom{n}{half}\),奇数时\(2lim \times \tbinom{n}{half}\)
再考虑 \(k≤lim\) 的情况,这个时候画图,会发现两边的条件被限定了,即左边的部分由于斜线下移,交点右移,因此左边的不满足 \(−k_i\) 时的情况,只能选\(+k_i\) ,右边的同理
这个时候贡献就由k的偏移情况来决定,左交点 \(L=max(1,l+k)\),右交点 \(R=min(n,r−k)\)
此时确定了\(−k_i L−1\)个,\(+k_i n−R\)个,这个时候中间还要选 \(2x+n−L+R=R−L+1\) 即 \(x=1−L+half\) 个
于是贡献\(\tbinom{R-L+1}{1-L+half}\)偶数,\(2\tbinom{R-L+1}{1-L+half}\) 奇数
当然,这个时候只需要暴力枚举 \(k\) 就好了
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7,N=2e5+5;
int T,fac[N],inv[N],n,l,r;
int qmi(int a,int b){
int res=1; while(b){if(b&1) res=res*a%mod; b>>=1; a=a*a%mod;} return res;
}
void init(){
fac[0]=1;for(int i=1;i<N;i++) fac[i]=(fac[i-1]*i)%mod;
inv[N-1]=qmi(fac[N-1],mod-2);
for(int i=N-2;~i;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int n,int m){
if(m<0||n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
init(); cin>>T;
while(T--){
scanf("%lld%lld%lld",&n,&l,&r);
int ans=0,lim=min(1-l,r-n);//枚举 k 的限制条件
ans=lim*C(n,n/2)%mod;
if(n&1) ans=(ans+lim*C(n,n/2)%mod);
for(int i=lim+1;;i++){
int L=max(1ll,l+i),R=min(n,r-i),rest=R-L+1;
if(rest<0) break;
ans=(ans+C(rest,n/2-(L-1)))%mod;
if(n&1) ans=(ans+C(rest,n/2+1-(L-1)))%mod;
}
printf("%lld\n",ans);
}
// system("pause");
return 0;
}