牛客练习赛140
A题
题意为给定一个目标字符串,长度为n,现在还有一个长度为m的正确密码,你可以修改这个正确密码,但是长度不能改变,问你是否可以通过修改正确密码,使得正确密码是目标字符串的字串。那么问题转化为在这个目标字符串中截取长度为m的字串,那么只需要判断一下m是否大于n即可,m大于n,输出-1,否则输出s.substr(0,m)即可。
时间复杂度为
void solve(){int n,m;cin >> n >> m;string s;cin >> s;if(m>n){cout << -1 << endl;}else{cout << s.substr(0,m) << endl;}
}
B题
知识点:分类讨论
题意为现在有n个岛屿,初始时在岛屿1,.假设现在在岛屿x,现在有3种操作:
问从1到达s需要至少需要多少次操作。
考虑从1到s的方式:
- 从1使用操作1到达s,需要s-1次操作
- 从1走到某一个位置使用操作3到达中间位置tmp,再从tmp到s,这里可能t比s大,可能比s小,可能刚好相等,只需要把这2种情况都考虑到就好了。
很显然如果要使用操作3,那么我们肯定会尽量到达距离s最近的地方,因此可以用得到一个向下取整的整数t,之后肯定有这个关系
,因为t是可能刚好整除。之后我们到达t和t+1之后使用操作3到达这两个位置,再使用操作1、2计算答案,然后取上述3种情况的最小值即可。
时间复杂度为
void solve(){int n,s;cin >> n >> s;int ans=s-1;int t=n/s;if(t*s==n){ans=min(ans,t-1+1);}else{int l=n/(t+1),r=n/t;ans=min(ans,t+1-1+1+s-l);ans=min(ans,t-1+1+r-s);}cout << ans << endl;
}
C题
知识点:构造
题意为给定一个长度为n的目标序列,现在有一个长度为n的序列,初始时全为0.每次操作为取当前序列的Mex,在序列中任选一个位置将其替换为这个Mex,现在可以进行不超过n次操作,问操作序列,如果不能变为目标序列,那么输出-1.
首先第一个因为每次取的是当前序列的Mex,即这个序列不包含这个数,那么替换之后序列中除了初始的0之外,没有一个元素相等,如果目标序列有除0外相等的元素,那么就不能变为目标序列。
接着考虑如何构造这个操作序列,我们可以考虑从目标序列往前推,为什么会这样考虑呢?因为每次得到的序列中的最大那个值一定是在上一轮替换过来的Mex,
0 2 3 4 5
0 0 0 0 1
0 2 0 0 1
0 2 3 0 1
0 2 3 4 1
0 2 3 4 5
这样就是一种可行的构造方法,当然也可以有其他的构造方法,肯定的是在有一个操作序列可以使得初始序列变为目标序列的情况下,我们总是可以这样去选择,因为到退回去发现要出现5,那么肯定0 1 2 3 4已经出现,所以这样是正确的,哈哈,不太会证明。
来一个不能过的例子0 0 0 3 4 5:
0 0 0 3 4 5
因为5本身可以是缺少元素1或者2中的一个,现在缺少2个元素,那么一定不能通过上一步变成
0 0 0 3 4 5,所以有一个充要条件就是除去当前最大元素,从0到max缺少的元素不能超过1个。比如例子0 0 1 3 4 5,那么上一步可以是0 0 1 3 4 2;或者0 0 1 2 3 4,那么上一步可以是0 0 1 2 3 0.
所以如果缺失0个元素,那么把最大值那个位置变为0;缺失1个元素就把最大值变为缺失那个元素即可。每次操作完检查一遍是否是全0.
所以我们按照这样的方式把操作的位置加进答案,最后逆序输出即可。
int get_max(int n){int res=0,mx=0;for(int i=1;i<=n;i++){if(a[i]>mx){mx=a[i];res=i;}}return res;
}
int get_cnt(int n,int mx){//统计从0到mx差几个数int res=0;vector<int> vis(mx+1,0);for(int i=1;i<=n;i++){if(!vis[a[i]]){vis[a[i]]=1;}}for(int i=0;i<=mx;i++) res+=vis[i]==0?1:0;return res;
}
int get_lose(int n,int mx){int res=0;vector<int> vis(mx+1,0);for(int i=1;i<=n;i++){if(!vis[a[i]]){vis[a[i]]=1;}}for(int i=0;i<=mx;i++)if(!vis[i]){res=i;break;}return res;
}
bool check(int n){int cnt=0;for(int i=1;i<=n;i++) cnt+=a[i]==0?1:0;return cnt==n;
}
void solve(){int n;cin >> n;for(int i=1;i<=n;i++) cin >> a[i];vector<int> t;for(int i=1;i<=n;i++) t.push_back(a[i]);sort(t.begin(),t.end());for(int i=0;i<n-1;i++){if(t[i]!=0&&t[i]==t[i+1]){cout << -1 << endl;return;}}if(check(n)){cout << 0 << endl;return;}vector<int> ans;for(int i=1;i<=n;i++){int id=get_max(n);int cnt=get_cnt(n,a[id]);if(cnt>1){cout << -1 << endl;return;}else if(cnt==1){//那么mx要变成缺的那个数int lose=get_lose(n,a[id]);a[id]=lose;ans.push_back(id);}else{ans.push_back(id);a[id]=0;}if(check(n)){break;}}cout << ans.size() << endl;for(int i=ans.size()-1;i>=0;i--) cout << ans[i] << " ";cout << endl;
}
时间复杂度为,因为n最大值才100,是可行的。
E题
知识点:状态压缩,位运算,线段树
题意为给定一个只包含小写字母的字符串,现在有两种操作:
操作1:选择字符串的从l到r的一段字串,将这段字串的每个字符都做的操作。
操作2:询问l到r字串是否可以通过重新排列之后变成回文串,是输出Yes,否则输出No。
这是一道典型的使用线段树维护区间修改和区间查询的题,这里难点在于怎么存储一个区间字符的信息。由于询问的是区间是否可以通过重排变为回文串,那么我们只关心这段区间每个字符出现的次数奇偶性,我们使用一个整形来存储26个字母奇偶性的状态。我们知道回文串可以为baab或者bab这两种形式,可以发现字符出现次数为奇数的最多只能出现一个,所以每次查询求一下这段区间状态state二进制表示中1的个数即可。可以使用一个函数__bulitin_popcount(num),这个函数会返回无符号整数二进制表示1的个数。
接着就是魔改线段树板子即可。
#include<bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define lc u<<1
#define rc u<<1|1
#define int long long
typedef long long ll;
typedef pair<int,int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int maxn=1e5+9;
struct Segment{int l,r;int lazy,state;//lazy为d次数,state为表示这一区间的状态
}tr[4*maxn+9];
string s;
void pushup(int u){tr[u].state=tr[lc].state^tr[rc].state;
}int change_state(int state,int d){d%=26;int res=0;for(int i=0;i<26;i++){if(((state>>i)&1)==1){res^=1<<((i+d)%26);}}return res;
}void pushdown(int u){if(tr[u].lazy){tr[lc].state=change_state(tr[lc].state,tr[u].lazy);tr[rc].state=change_state(tr[rc].state,tr[u].lazy);tr[lc].lazy+=tr[u].lazy;tr[rc].lazy+=tr[u].lazy;tr[u].lazy=0;}
}void build(int u,int l,int r){tr[u]={l,r,0,0};if(l==r){tr[u].state=1<<(s[l]-'a');return;}int mid=(l+r)/2;build(lc,l,mid),build(rc,mid+1,r);pushup(u);
}//区间修改
void update(int u,int l,int r,int z){if(tr[u].l>=l&&tr[u].r<=r){tr[u].state=change_state(tr[u].state,z);tr[u].lazy+=z;return;}//否则查询左右儿子pushdown(u);int mid=(tr[u].l+tr[u].r)/2;if(l<=mid) update(lc,l,r,z);if(r>mid) update(rc,l,r,z);pushup(u);
}//区间查询
int query(int u,int l,int r){if(tr[u].l>=l&&tr[u].r<=r){return tr[u].state;}//下传懒标记pushdown(u);int res=0LL;int mid=(tr[u].r+tr[u].l)/2;if(l<=mid) res^=query(lc,l,r);if(r>mid) res^=query(rc,l,r);return res;
}void solve(){int n,q;cin >> n >> q;cin >> s;s=" "+s;build(1,1,n);while(q--){int op,l,r,k;cin >> op >> l >> r;if(op==1){cin >> k;update(1,l,r,k);}else if(op==2){int res=query(1,l,r);int cnt=__builtin_popcount(res);if(cnt>1) cout << "No" << endl;else cout << "Yes" << endl;}}
}signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);int t=1;cin >> t;while(t--){solve();}return 0;
}
主播在最后2分钟搓出代码,结果在change_state中的if(((state>>i)&1)==1)出现错误,写成了if((state&(1<<i))==1)出错了,导致主播这道题没过,赛后调试发现这个错误。还是避免犯小错误。
D题、F题没有思路。