A
题目分析
签到
C++代码
#include<iostream>
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
cout<<n/4+n%4/2<<endl;
}
}
B
题目分析
将n*n的方格分成若干个k*k的方格,每个k*k的方格中所有的数都相同
遍历每个k*k的第一个数即可,然后存入另一个(n/k)*(n/k)的数组中
C++代码
#include<iostream>
using namespace std;
const int N=1010;
char g[N][N];
int n,k;
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)scanf("%s",g[i]+1);
char t[n/k+1][n/k+1];
for(int i=1,cnt=1;i<=n;i+=k,cnt++){
for(int j=1,cnt1=1;j<=n;j+=k,cnt1++){
t[cnt][cnt1]=g[i][j];
}
}
for(int i=1;i<=n/k;i++){
for(int j=1;j<=n/k;j++)
cout<<t[i][j];
cout<<endl;
}
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}
C
题目分析
设置两个数组预处理字符串a和b的字母出现次数的前缀和
sa[N][26]:sa[i][j]表示字符串a中第1~i个字符中字母(j+'a')出现的个数
sb[N][26]:sb[i][j]表示字符串b中第1~i个字符中字母(j+'a')出现的个数
用递推的方式更新sa和sb数组
之后处理询问就计算[l,r]中字符串a和字符串b出现的所有字母的个数之差,把它们都加起来,然后除以2就是答案了
C++代码
#include<iostream>
using namespace std;
const int N=200010;
char a[N],b[N];
int n,q;
void solve(){
scanf("%d%d",&n,&q);
scanf("%s%s",a+1,b+1);
int sa[n+5][26]={0},sb[n+5][26]={0};
for(int i=1;i<=n;i++){
sa[i][a[i]-'a']++;
sb[i][b[i]-'a']++;
//用i-1时的状态更新i的状态
for(int j=0;j<26;j++){
sa[i][j]+=sa[i-1][j];
sb[i][j]+=sb[i-1][j];
}
}
while(q--){
int l,r,ans=0;
scanf("%d%d",&l,&r);
for(int i=0;i<26;i++){
//a的[l,r]中字母i+'a'的个数和b的[l,r]中字母i+'a'的个数之差
ans+=abs((sa[r][i]-sa[l-1][i])-(sb[r][i]-sb[l-1][i]));
}
printf("%d\n",ans/2);
}
}
int main(){
int t;
scanf("%d",&t);
while(t--){
solve();
}
return 0;
}
D
题目分析
ab+ac+bc≤n,a+b+c≤x(a,b,c都大于等于1)
枚举三个数肯定是不可取的,那我们可以只枚举a和b两个数,然后判断c有几种选法,然后加入到答案中去就行
ab+ac+bc≤n => ab<n a+b+c≤x => a+b<x
ab+ac+bc≤n =>c≤(n-ab)/(a+b)
a+b+c≤x => c≤x-a-b
所以:c ≤ min ((n-ab)/(a+b) , x-a-b)
C++代码
#include<iostream>
using namespace std;
typedef long long LL;
void solve(){
int n,x;
cin>>n>>x;
LL ans=0;
for(int a=1;a<min(n,x);a++)
for(int b=1;a*b<n&&a+b<x;b++){
//总方案数加上当前情况下c可选的方案数
ans+=min((n-a*b)/(a+b),x-a-b);
}
cout<<ans<<endl;
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}
E
题目分析
任意区间包含多少个符合条件的区间的转换成符合条件的区间被多少个区间包含
假设区间[i,j]符合条件,则包含该区间的区间有i*(n-j+1)个
字符串只包含0和1,要找的区间0和1一样多,所以我们可以把0变成-1,转换成要找区间和为0的区间
所以我们枚举每个区间的右端点,但是暴力找左端点肯定不行,所以要挖掘点性质
设置一个前缀和数组sum,
如果sum[i]==sum[j],则区间[i+1,j]的区间和为0,对答案的贡献为(i+1)*(n-j+1)
如果sum[k]==sum[j],则区间[k+1,j]的区间和为0,对答案的贡献为(k+1)*(n-j+1)
加起来就是(i+1+k+1)*(n-j+1)
由此可见,我们从前往后枚举每一个右端点j,找到在此之前的前缀和等于sum[j]的数,i+1就可以是区间的左端点,然后就可以用map存储每个相同前缀和对应的左端点的和
C++代码
#include<iostream>
#include<vector>
#include<map>
using namespace std;
typedef long long LL;
const int N=200010,mod=1e9+7;
void solve(){
string s;
cin>>s;
LL n=s.size();
s=' '+s;
vector<int> a(n+5),sum(n+5);
for(int i=1;i<=n;i++)a[i]=(s[i]=='0'?-1:1);//0变成-1,1还是1
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
map<int,LL> mp;
mp[0]=1;
LL ans=0;
for(int i=1;i<=n;i++){
ans+=mp[sum[i]]*(n-i+1)%mod;//以i为右端点的所有左端点的和为mp[sum[i]]
mp[sum[i]]+=(i+1);
}
cout<<ans%mod<<"\n";
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}
F
题目分析
二分操作的数中最小的数
C++代码
#include<iostream>
using namespace std;
typedef long long LL;
const int N=200010;
int a[N],b[N];
LL score[N],ans;
int n,k;
bool check(LL x){
LL cnt=0,res=0,sumx=0;
for(int i=1;i<=n;i++){
LL t=(a[i]-x+b[i]-1)/b[i];//上取整,先把所有大于x的数都算上
LL p=a[i]-t*b[i];//剩余的最大的数
if(t<0)continue;
if(p&&p==x)sumx++;//剩余的最大的数为x,则x的个数加一
cnt+=t;//操作次数+t
res+=min(score[i],t*a[i]-(t-1)*t/2*b[i]);
}
if(cnt<=k)ans=max(ans,min(k-cnt,sumx)*x+res);
return cnt<=k;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
cin>>b[i];
LL t=a[i]/b[i];
//score[i]表示靠第i个元素最多可以获得的分数
score[i]=a[i]%b[i]+t*a[i]-(t-1)*t*b[i]/2;
}
ans=0;
//二分k次操作中获得的最少的一个分数
int l=0,r=1e18;
while(l<r){
LL mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<ans<<endl;
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}