补题/6.7~6.8(牛客练习赛140)(ABC408)
真是越来越菜了,牛客已经掉到蓝了(悲
6.7/21:57:喜报,ABC终于能自己A5题了,虽然好像这场前五题比之前简单
题意:略
思路:
666,p=1时,只有第一位为1,其余位都是0
void solve(){
int d,p;cin>>d>>p;
int k;
int td=d,tp=p;
if(d%p==0){
k=1;
}else{
int gcd=__gcd(d,p);
p/=gcd;
k=p;
}
int q;cin>>q;
while(q--){
int l,r;cin>>l>>r;
if(td==tp){
cout<<1<<endl;continue;
}
if(p==1){
if(l==1&&r>1)cout<<2<<endl;
else{
cout<<1<<endl;
}
}
else cout<<min(r-l+1,k)<<endl;
}
}
题意:
想从1到s,每次向左或向右或跳到n/x的位置,求跳的最小次数
思路:
本来以为是经典BFS,结果又T又M
发现范围为1e18
显然需要数学结论直接推导得到答案
模拟得到当s在大于n/2的位置时,可以先跳到n在往回走,或者跳到n/2处往前走
s在小于n/2时,可以一直往前走,或者跳到s左边某个位置,或者跳到s右边某个位置,然后再往前或往回走
void solve(){
cin>>n>>s;
int res=0;
if(s==1){
cout<<0<<endl;return;
}
if(s<=n/2){
int pos=n/s+2;
res=s-1;
if(pos<=s){
res=min(res,(n/s-1)+1+abs(n/(n/s)-s));
res=min(res,(n/s+1-1)+1+abs(n/(n/s+1)-s));
res=min(res,(n/s-1-1)+1+abs(n/(n/s-1)-s));
}
}else{
int pos=2;
res=min(n-s+1,pos-1+1+(s-n/2));
}
cout<<res<<endl;
}
题意:
一个长度为n的初始全0序列,每次可以取序列的mex并将序列的任意一个数换为mex
求转化为给定序列的最小次数(小于等于n次)以及操作
思路:
先考虑无法生成给定序列的情况
1.序列某一个大于0的数出现超过两次,显然当一个数已经在序列中出现,那mex就一定不会是它,因此只能出现一次
2.序列最大值小于等于n,如果大于n,显然操作次数大于n次
3.序列大于0的小于最大值的数有大于等于2个的数没有出现
模拟后发现要么是mex从小至大依次填入。要么是中间某个数没有在序列中出现,此时这个数要先放在最大数的位置上。
void solve(){
int n;cin>>n;
vector<int>a(n+1);
vector<int>cnt(1000);
int mx=0;
rep(i,1,n){
cin>>a[i];
cnt[a[i]]++;
mx=max(mx,a[i]);
}
if(mx>n){
cout<<-1<<endl;return;
}
int k=0;
rep(i,0,mx){
if(!cnt[i])k++;
}
if(k>=2){
cout<<-1<<endl;return;
}
rep(i,1,mx){
if(cnt[i]>=2){
cout<<-1<<endl;return;
}
}
cout<<mx<<endl;
int mex=1;
while(mex<=mx){
if(cnt[mex]){
rep(i,1,n){
if(a[i]==mex){
cout<<i<<' ';break;
}
}
}else{
rep(i,1,n){
if(a[i]==mx){
cout<<i<<' ';break;
}
}
}
mex++;
}
cout<<endl;
}
题意:
定义一个小写字母s施加一次魔法后变为(char)s+1。当s为z时,施加魔法后变为a
给定一个字符串s,q次查询。维护两种操作
1.对于[l,r]内的字符施加D次魔法
2.查询[l,r]的字符子串是否能通过重排变为回文串
思路:
一个字符串能被重排为回文串的充要条件为:26个字母中最多出现一个奇数次的
(长度为偶数:次数都为偶(显然如果有一个奇次,那么长度就为奇数),长度为奇数:只有一个奇次)
将26个字符进行状压映射,a:001,b:010,c:100,d:1000以此类推
那么对于一个[l,r]范围,可以使用异或来统计次数,如果异或后的数二进制位1的个数小于等于一,那么符合条件
对于操作1,快速改变一个[l,r]范围的字符,相当于对这个区间每个数进行取模的左移
用线段树维护
struct node{
int l,r;
int sum;
int tag;
}tr[4*maxn];
void pushup(int p){
tr[p].sum=tr[ls].sum^tr[rs].sum;
}
void move(int &x,int d){
if(d%26==0)return;
int res=0;
for(int i=0;i<=25;i++){
if((1ll<<i)&x){
int t=i+(d%26);
t%=26;
res|=(1ll<<t);
}
}
x=res;
}
void pushdown(int p){
if(tr[p].tag){
tr[ls].tag+=tr[p].tag;
tr[rs].tag+=tr[p].tag;
move(tr[ls].sum,tr[p].tag);
move(tr[rs].sum,tr[p].tag);
tr[p].tag=0;
}
}
void build(int p,int l,int r,vector<int>&a){
tr[p].l=l;tr[p].r=r;tr[p].tag=0;
if(l==r){
tr[p].sum=a[l];
return;
}
int mid=l+r>>1;
build(ls,l,mid,a);build(rs,mid+1,r,a);
pushup(p);
}
void change(int p,int l,int r,int d){
if(l<=tr[p].l&&tr[p].r<=r){
tr[p].tag+=d;
move(tr[p].sum,d);
return;
}
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)change(ls,l,r,d);
if(r>mid)change(rs,l,r,d);
pushup(p);
}
int query(int p,int l,int r){
if(l<=tr[p].l&&tr[p].r<=r){
return tr[p].sum;
}
int res=0;
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)res^=query(ls,l,r);
if(r>mid)res^=query(rs,l,r);
return res;
}
int count(int x){
int res=0;
for(int i=0;i<=25;i++){
if(x&(1ll<<i))res++;
}
return res;
}
void solve(){
int n,q;cin>>n>>q;
string s;cin>>s;
s=" "+s;
vector<int>a(n+1);
rep(i,1,n)a[i]=1ll<<(s[i]-'a');
build(1,1,n,a);
while(q--){
int opt;cin>>opt;
int l,r;cin>>l>>r;
if(opt==1){
int d;cin>>d;
change(1,l,r,d%26);
}else{
int k=query(1,l,r);
int num=count(k);
if(num<=1){
cout<<"Yes"<<endl;
}else cout<<"No"<<endl;
}
}
}
题意:
给定一个01串,可将字符翻转,求使其只出现一个连续的1段的最小翻转次数
思路:
设区间[l,r]为1段
则[1,l-1],[r+1,n]为0段
答案为0段的1的个数+1段的0的个数
枚举r,求2xpre[i]-i的前缀最小值即可
void solve(){
int n;cin>>n;
string s;cin>>s;s=" "+s;
vector<int>pre(n+1);
rep(i,1,n){
pre[i]=pre[i-1]+(s[i]-'0');
}
int ans=1e9;
vector<int>x(n+1);
rep(i,0,n){
x[i]=2*pre[i]-i;
}
rep(i,1,n){
x[i]=min(x[i-1],x[i]);
}
rep(i,1,n){
ans=min(ans,pre[n]-(2*pre[i]-i)+x[i]);
}
cout<<ans<<endl;
}
题意:
给定一颗树,求1到n的最小按位或路径
思路:
让答案二进制位从高位到低位枚举i
看能不能令1到n的按位或路径上 没有路径的二进制 或上i 是1
取那些路径或上i不是1的,并且 排除 路径或上之前枚举的i为1且答案不包含i的 ,观察1和n是否连通
前者好理解,后者是为了保证对答案贡献小的位(1 2 4 8 16...发现2^i > (2^0+2^1+..2^(i-1)) 不能破坏之前的贪心策略
如果不连通,说明最小按位或路径一定包含i
使用并查集判断连通性
int n,m;
int f[maxn];
int find(int x){
if(f[x]!=x){
f[x]=find(f[x]);
}return f[x];
}
void merge(int x,int y){
if(find(x)!=find(y)){
f[find(x)]=find(y);
}
}
void solve(){
cin>>n>>m;
vector<pair<pii,int>>edge(m+1);
vector<int>k(32);
rep(i,1,m){
int u,v,w;cin>>u>>v>>w;
edge[i]={{u,v},w};
}
int ans=0;
for(int i=32;i>=0;i--){
rep(j,1,n){
f[j]=j;
}
for(int j=1;j<=m;j++){
auto[x,w]=edge[j];
int u=x.fi,v=x.se;
int ok=1;
for(int z=32;z>i;z--){
if((w&(1ll<<z))&&(!k[z])){
ok=0;
}
}
if(!ok)continue;
if(!((1ll<<i)&w)){
merge(u,v);
}
}
if(find(n)!=find(1)){
ans|=(1ll<<i);
k[i]=1;
}
}
cout<<ans<<endl;
}
题意:
给定一个排列代表高度,可以任意挑选一个起点,可以跳到与现在高度差大于等于D且在位置为中心,半径为r的范围内的的点
求最多能跳多少次
思路:
记dp[i]为到达高度为i最多能跳多少次
考虑从低到高1->n转移
因为高度是排列,显然一个i是从[1,i-D]中最大的dp值+1转移得到的
且有个限制,即它是从限制范围内转移得到的
求区间dp最大值可以用线段树维护
用队列控制i-D何时在线段树中插入
int dp[maxn];
struct node{
int l,r;
int mx;
}tr[4*maxn];
void pushup(int p){
tr[p].mx=max(tr[ls].mx,tr[rs].mx);
}
void build(int p,int l,int r){
tr[p].l=l;tr[p].r=r;
if(l==r){
tr[p].mx=0;return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(p);
}
void change(int p,int pos,int x){
if(tr[p].l==tr[p].r&&tr[p].l==pos){
tr[p].mx=x;
return;
}
int mid=tr[p].l+tr[p].r>>1;
if(pos<=mid)change(ls,pos,x);
else change(rs,pos,x);
pushup(p);
}
int query(int p,int l,int r){
if(l<=tr[p].l&&tr[p].r<=r){
return tr[p].mx;
}
int res=0;
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)res=max(res,query(ls,l,r));
if(r>mid)res=max(res,query(rs,l,r));
return res;
}
void solve(){
int n,d,r;cin>>n>>d>>r;
vector<int>a(n+1);
vector<int>pos(n+1);
rep(i,1,n){
cin>>a[i];
pos[a[i]]=i;
}
build(1,1,n);
queue<int>q;
rep(i,1,n){
if(i-d>=1){
int u=q.front();q.pop();
change(1,pos[u],dp[u]);
}
int res=query(1,max(1ll,pos[i]-r),min(n,pos[i]+r));
dp[i]=max(dp[i],query(1,max(1ll,pos[i]-r),min(n,pos[i]+r))+1);
q.push(i);
}
int ans=0;
rep(i,1,n){
ans=max(ans,dp[i]);
// debug(dp[i]);
}
cout<<max(0ll,ans-1)<<endl;
}
题意:
给定n,求n!个排列中,mid在连续区间的连乘积
思路:
考虑O(n^2)的求贡献做法
考虑将1~n中每一个数作为区间的mid,单独算出每一个数对答案的贡献
枚举排列的这个数作为mid的区间长度len
len从1~n
类似插空法的思想其他不在区间的数排列有(n-len)!种方法
由于mid定义是自动排好序的,所有区间内的数有len!种方法
把区间插入到不在区间的数排列中有(n-len+1)种方法
考虑这个数作为mid,那么区间内[1,len/2]个数是从小于它的数中取的
[len/2+2,len]是从大于它的数中取的
用预处理组合数的方式快速求解
1~n中一个数i对答案的贡献是:上面的乘积之和作为幂,i作为底
答案为 每个数贡献的乘积
由于幂可能很大,因为mod为质数,所以可以欧拉降幂,即 将幂次取模mod-1,其他的直接取模mod
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int C[6005][6005],fact[6005];
void init(){
fact[0]=1;
for(int i=1;i<=6001;i++){
fact[i]=fact[i-1]*i%mod_phi;
}
for(int i=0;i<=6001;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j<=i-1;j++){
C[i][j]=(C[i-1][j]%mod_phi+C[i-1][j-1]%mod_phi)%mod_phi;
}
}
}
void solve(){
int n;cin>>n;
init();
int ans=1;
for(int i=1;i<=n;i++){
int cnt=0;
for(int j=1;j<=n;j++){
int x=i-1,y=n-i;
int u=j/2,v=j-1-u;
int res=((((C[x][u]*C[y][v]%mod_phi)*fact[j]%mod_phi)*fact[n-j]%mod_phi)*(n-j+1)%mod_phi);
cnt=(cnt%mod_phi+res%mod_phi)%mod_phi;
}
ans=ans*ksm(i,cnt)%mod;
}
cout<<ans<<endl;
}
题意:
给定一给abc字符串,q次换指定字符的机会,求字典序最小的结果
思路:
a一定不换
b->a 优先级高于 b->c->a
c->a 优先级高于 c->b->a 高于 c->b
将每种操作的时间点存下来,再考虑换不换
遍历s,贪心的一定先把前面b和c能换的换了
用set二维数组+二分的方式进行操作
如果能操作,需要统计后把set存的时间点给删除掉
void solve(){
int n,q;cin>>n>>q;
string s;cin>>s;
s=" "+s;
set<int>st[3][3];
rep(i,1,q){
char a,b;cin>>a>>b;
st[a-'a'][b-'a'].insert(i);
}
rep(i,1,n){
if(s[i]=='a')continue;
//b->a
//b->c->a
if(s[i]=='b'){
if(st[1][0].empty()){
if(st[1][2].empty())continue;
//c->a
//b->c
auto it=st[2][0].lower_bound(*st[1][2].begin());
if(it!=st[2][0].end()){
s[i]='a';
st[1][2].erase(st[1][2].begin());
st[2][0].erase(it);
}
}else{
s[i]='a';
st[1][0].erase(st[1][0].begin());
}
}
//c->b->a
if(s[i]=='c'){
if(st[2][0].empty()){
if(st[2][1].empty())continue;
s[i]='b';
auto it=st[1][0].lower_bound(*st[2][1].begin());
if(it!=st[1][0].end()){
s[i]='a';
st[1][0].erase(it);
}
st[2][1].erase(st[2][1].begin());
}else{
s[i]='a';
st[2][0].erase(st[2][0].begin());
}
}
}
rep(i,1,n){
cout<<s[i];
}
cout<<endl;
}

浙公网安备 33010602011771号