A.2026
%4讨论即可,每4位有2个2,202620262026%4为剩下的单独计算
B:子序列
钦定当前遍历到j
cnt1[i]记录j以前,%6==i的当前下标个数
cnt2[i]记录j以前,%6==i的当前下标两两组合个数
所以cnt2[(6-a[j])%6]即为以当前下标的合法子序列个数
注意是先更新ans再更新cnt2[i]和cnt1[i]
时间复杂度:O(n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=5e6+10;
int cnt1[10];
int cnt2[10];
int a[N];
void solve(){
int n=0;
for(int i=1;i<=2026;i++){
a[++n]=2;
a[++n]=0;
a[++n]=2;
a[++n]=6;
}
int ans=0;
for(int i=1;i<=n;i++){
int to=(6-a[i])%6;
ans=(ans+cnt2[to])%mod;
for(int j=0;j<6;j++){
cnt2[(a[i]+j)%6]+=cnt1[j];
}
cnt1[a[i]%6]++;
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
C:天气之子
按照题意模拟即可
时间复杂度:O(n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
int a[N];
int sta[N];
int cnt[N];
void solve(){
int n;
cin>>n;
int ans=0;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sta[a[1]]=1;
cnt[a[1]]=1;
int fir=a[1];
int flag=1;//直接作为上次成功预报的
if(a[1]==1){
ans++;
}else{
flag=0;
}
for(int i=2;i<=n;i++){
if(flag){
if(flag==a[i]){
ans++;
}else{
flag=0;
}
}else{
if(fir==a[i]){
flag=fir;
ans++;
}
}
//cout<<"ans: "<<ans<<'\n';
cnt[a[i]]++;
if(!sta[a[i]])sta[a[i]]=i;
if(cnt[a[i]]>cnt[fir]||cnt[a[i]]==cnt[fir]&&sta[a[i]]>sta[fir]){
fir=a[i];
}
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
D:自动驾驶
用map判重即可
时间复杂度O(knlog(n)), 转换成4进制的话可以O(kn)
#include<bits/stdc++.h>
using namespace std;
#define int long long
map<int,int>mp;
int cal(string s){
int sum=0;
for(int i=0;i<s.size();i++){
sum=sum*26+(s[i]-'A');
}
return sum;
}
void solve(){
int n,k;
cin>>n>>k;
string s;
cin>>s;
s='!'+s;
int ans=0;
for(int i=1;i<=n-k+1;i++){
string t="";
for(int j=i;j<=i+k-1;j++){
t+=s[j];
}
int num=cal(t);
mp[num]++;
if(mp[num]==2)ans++;
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
E:体育课
杨辉三角,我们来简单证明一下.
从(1,1)开始,想要走到底部必须向右走n-1次,所以(1,1)对于底部的贡献为C(n-1,0)次.以此类推,(1,i)对于底部的贡献为C(n-1,i)次,所以dfs过程中可以直接把底部sum算出来
时间复杂度O(n!)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
int C[15][15];
void init(){
C[0][0]=1;
C[1][0]=1;C[1][1]=1;
for(int i=2;i<=12;i++){
C[i][0]=1;
for(int j=1;j<=i-1;j++){
C[i][j]=C[i-1][j]+C[i-1][j-1];
//cout<<C[i][j]<<'\n';
}
C[i][i]=1;
}
}
int vis[19];
int p[19];
int n,sum;
int flag=0;
void dfs(int pos,int ans){
if(flag)return;
if(ans>sum)return;
if(pos==n+1&&!flag){
if(ans==sum){
for(int i=1;i<=n;i++){
cout<<p[i]<<" ";
}
cout<<'\n';
flag=1;
}
return;
}
for(int i=1;i<=n;i++){
if(vis[i])continue;
vis[i]=1;
p[pos]=i;
dfs(pos+1,ans+C[n-1][pos-1]*i);
vis[i]=0;
}
}
void solve(){
cin>>n>>sum;
if(n==1){
cout<<1;
return;
}
init();
// for(int i=0;i<=4;i++){
// cout<<C[4][i]<<" ";
// }
// cout<<'\n';
dfs(1,0);
//cout<<sum;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
F:手套
配对尽可能多的手套,就是配对min(n,m)对
发现答案单调,允许的最大丑陋程度越大,能配对的数量越多
我们先把两个数组排序,遍历元素数量大小小的数组设为a[i],大的设为b[i],对于每一个a[i],我们尽量让配对他的b[j]在允许的情况下尽量小,因为a[i+1]选的b比a[i]小的话,两者交换必定满足,而反之不一定满足.所以双指针就行
时间复杂度O(nlog(n))
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int a[N],b[N];
int n,m;
int check(int mid){
int l=1;
for(int i=1;i<=n;i++){
while(l<=m&&abs(a[i]-b[l])>mid){
l++;
}
if(l>m)return 0;
l++;
}
return 1;
}
void solve(){
cin>>n>>m;
//默认n<=m
//a服务n
//b服务m
if(n<=m){
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=m;i++){
cin>>b[i];
}
sort(b+1,b+m+1);
}else{
for(int i=1;i<=n;i++){
cin>>b[i];
}
sort(b+1,b+n+1);
for(int i=1;i<=m;i++){
cin>>a[i];
}
sort(a+1,a+m+1);
swap(n,m);
}
int l=0,r=1e9,ans=1e9;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){
ans=mid;
r=mid-1;
}else{
l=mid+1;
}
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
G:字符串
观察数据范围,猜测大概是O(n^3)的做法.
做这道题你要有一个冒泡排序的前置知识:交换次数为数组中的逆序对个数
定义dp[i][j][k][num]为当前选了i个0,j个1,k个2,当前位(第i+j+k位)选择num
为了转移方程,我们必须枚举上一个状态dp[ii][jj][kk][t],ii,jj,kk都可以直接算出,t的话需要枚举,注意要把不合法的情况,比如t对应的ii/jj/kk==0,或者t==num,都是不合法的
现在我们要计算包括当前位(第i+j+k位)的逆序对个数,我们可以预处理出第pos个0/1/2的位置,然后就可以知道当前(第i+j+k位)在原字符串中对应的下标lock,我们可以通过前缀和算出lock前面有几个0,几个1,几个2,设now为不等于当前位的数值,如果lock前的now个数cnt1 小于当前选了的now的个数cnt2,那么就肯定会产生cnt2-cnt1个逆序对
时间复杂度O(len[0]*len[1]*len[2]*小常数(大约20))
#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
//#define int long long
#define i128 __int128
#define pii pair<int,int>
#define tp tuple<int,int,int>
#define db double
const db INF=1e18;
const db pi=acos(-1);
const int mod=1e9+7;
const int inv2=500000004;
const db eps=1e-6;
const int inf=1e18;
const int N=4e2+10;
const int base=499;
int dcmp(double x){
if(fabs(x)<eps){
return 0;
}
return x<0?-1:1;
}
void debug(){
cout<<"Test:Wrong"<<'\n';
exit(0);
}
int num[N];
int len[3];
int st[N][3];
int pre[N][3];
int f(int i,int j,int k,int op){
int cnt=0;
int pos=i+j+k-1;
vector<int>tmp(3,0);
int lock;
if(op==0)lock=st[i][op];
if(op==1)lock=st[j][op];
if(op==2)lock=st[k][op];
tmp[0]=i;
tmp[1]=j;
tmp[2]=k;
for(int num=0;num<3;num++){
if(num!=op){
if(pre[lock][num]<tmp[num]){
cnt+=(tmp[num]-pre[lock][num]);
}
}
}
return cnt;
}
void solve(){
string s;
cin>>s;
int n=s.size();
s='!'+s;
for(int i=1;i<=n;i++){
int num=s[i]-'0';
++len[num];
st[len[num]][num]=i;
pre[i][num]++;
for(int j=0;j<3;j++){
pre[i][j]+=pre[i-1][j];
}
}
int dp[len[0]+1][len[1]+1][len[2]+1][3];//就当前而言最后一步的选择
//cout<<len[0]<<" "<<len[1]<<" "<<len[2]<<'\n';
//40的话就非常不合理,为什么这么说呢?
memset(dp,0x3f,sizeof(dp));
//状态转移方程由之前的来,第一步是不需要关注的
if(len[0])dp[1][0][0][0]=0;
if(len[1])dp[0][1][0][1]=0;
if(len[2])dp[0][0][1][2]=0;
//133*133*133*常数
for(int i=0;i<=len[0];i++){
for(int j=0;j<=len[1];j++){
for(int k=0;k<=len[2];k++){
if(i+j+k>=2){
for(int num=0;num<3;num++){
int ii=i;
int jj=j;
int kk=k;
if(num==0)ii--;
if(num==1)jj--;
if(num==2)kk--;
//之前的选择
if(ii>=0&&jj>=0&&kk>=0){
for(int t=0;t<3;t++){
int flag=0;
if(t==0&&ii>=1)flag=1;
if(t==1&&jj>=1)flag=1;
if(t==2&&kk>=1)flag=1;
if(flag&&t!=num){
//相隔必须合法才能接受
//我们可以快速查询出前面本来应该有多少个,本来应该有多少个,现在实际有多少个
int cost=f(i,j,k,num);
dp[i][j][k][num]=min(dp[i][j][k][num],dp[ii][jj][kk][t]+cost);
}
}
}
}
}
}
}
}
int g=500*500;
if(min({dp[len[0]][len[1]][len[2]][0],dp[len[0]][len[1]][len[2]][1],dp[len[0]][len[1]][len[2]][2]})>g){
cout<<-1;
}else{
cout<<min({dp[len[0]][len[1]][len[2]][0],dp[len[0]][len[1]][len[2]][1],dp[len[0]][len[1]][len[2]][2]});
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}
H:数列操作
单点修改,可以想到线段树
对于的线段树一个节点对应的区间[l,r]
有三种可能的合法区间最小值
[l,mid]的答案,[mid+1,r]的答案,以及要跨过mid,mid+1的中间区间的答案
我们可以计算出每个区间最左侧的i的下标设为L[i],最右侧的i的下标设为R[i]
我们需要的则是左子树的R数组和右子树的L数组,这样可以使区间最短,并把两个数组分别按照下标降序和升序排序
我们可以先把左子树的R数组全部取完,然后再双指针右子树的L数组,通过双指针可以确定出右子树的L数组取下标前i小的几个数,左子树需要取的数的最小值j.
如果合法(即type==k,type是当前取的区间有几种不同的数),则用pos[j]-pos[i]+1来更新答案ans
时间复杂度O(m*log(n)30log(30))
#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define int long long
#define i128 __int128
#define pii pair<int,int>
#define tp tuple<int,int,int>
#define db double
const db INF=1e18;
const db pi=acos(-1);
const int mod=1e9+7;
const int inv2=500000004;
const db eps=1e-6;
const int inf=1e18;
const int N=5e4+10;
const int base=499;
int dcmp(double x){
if(fabs(x)<eps){
return 0;
}
return x<0?-1:1;
}
void debug(){
cout<<"Test:Wrong"<<'\n';
exit(0);
}
int n,k,m;
struct node{
int ans;
int R[40],L[40];
}tr[N<<2];
int a[N];
int ls(int p){
return p*2;
}
int rs(int p){
return p*2+1;
}
//唯一难点在于pushup
struct group{
int pos,num;
}ll[40],rr[40];
int cnt[40];
bool cmpl(group n1,group n2){
return n1.pos>n2.pos;
}
bool cmpr(group n1,group n2){
return n1.pos<n2.pos;
}
void pushup(int p){
tr[p].ans=min(tr[ls(p)].ans,tr[rs(p)].ans);
int totl=0,totr=0;
for(int i=1;i<=k;i++){
cnt[i]=0;
tr[p].L[i]=min(tr[ls(p)].L[i]==0?inf:tr[ls(p)].L[i],tr[rs(p)].L[i]==0?inf:tr[rs(p)].L[i]);
tr[p].R[i]=max(tr[ls(p)].R[i],tr[rs(p)].R[i]);
if(tr[p].L[i]==inf){
tr[p].L[i]=0;
}
if(tr[ls(p)].R[i])ll[++totl]={tr[ls(p)].R[i],i};
if(tr[rs(p)].L[i])rr[++totr]={tr[rs(p)].L[i],i};
}
sort(ll+1,ll+totl+1,cmpl);
sort(rr+1,rr+totr+1,cmpr);
int type=0;
for(int i=1;i<=totl;i++){
cnt[ll[i].num]++;
type++;
}
int ans=inf;
for(int i=1;i<=totr;i++){
cnt[rr[i].num]++;
if(cnt[rr[i].num]==1){
type++;
}
while(totl>=1&&cnt[ll[totl].num]==2){
cnt[ll[totl].num]--;
totl--;
}
if(type==k&&totl>=1){
ans=min(ans,rr[i].pos-ll[totl].pos+1);
}
}
tr[p].ans=min(tr[p].ans,ans);
}
void build(int p,int s,int t){
memset(tr[p].L,0,sizeof(tr[p].L));
memset(tr[p].R,0,sizeof(tr[p].R));
tr[p].ans=inf;
if(s==t){
tr[p].L[a[s]]=s;
tr[p].R[a[s]]=s;
if(k==1)tr[p].ans=1;
return;
}
int mid=(s+t)>>1;
build(ls(p),s,mid);
build(rs(p),mid+1,t);
pushup(p);
}
void update(int p,int s,int t,int l,int r,int x){
if(l<=s&&t<=r){
memset(tr[p].L,0,sizeof(tr[p].L));
memset(tr[p].R,0,sizeof(tr[p].R));
tr[p].R[x]=s;
tr[p].L[x]=s;
return;
}
int mid=(s+t)>>1;
if(l<=mid){
update(ls(p),s,mid,l,r,x);
}
if(mid+1<=r){
update(rs(p),mid+1,t,l,r,x);
}
pushup(p);
}
void solve(){
cin>>n>>k>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
for(int i=1;i<=m;i++){
int op;
cin>>op;
if(op==1){
int p,v;
cin>>p>>v;
update(1,1,n,p,p,v);
}else{
if(tr[1].ans!=inf){
cout<<tr[1].ans<<'\n';
}else{
cout<<-1<<'\n';
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T=1;
//cin>>T;
while(T--)solve();
return 0;
}



京公网安备 11010502036488号