省赛第一次训练赛题解
A(CF1451C)
由于没有操作次数的限制,很容易想到对于任意 k 个相同的字符,他们都可以通过交换换到一起之后修改。而对于两个按照字母顺序排序之后相同的字符串,从第一个也可以经过若干交换换到第二个。
所以问题变成了:在两个字符序列中,一次可将 k 个相同的字母变为它的下一个,问是否能把第一个变成第二个。
注意数据,不能直接a,b进行排序。但是可以统计字母出现次数,然后从字母 a
开始到字母 z
,每次把第一个串中当前字母的个数,通过变成下一个字母,变得和第二个串的个数一样,如果不可行就输出 No
。这样就线性通过此题。
#include<bits/stdc++.h>
using namespace std;
int a[100010];
void slove(){
string s1,s2;
int n,k;
cin>>n>>k;
cin>>s1>>s2;
int a[30]={0},b[30]={0};
for(auto ch:s1){
a[ch-'a']++;
}
for(auto ch:s2){
b[ch-'a']++;
}
for(int i=0;i<26;i++)
if(a[i]>=b[i]){
if((a[i]-b[i])%k!=0) {
cout << "No" << '\n';
return ;
}
else
a[i+1]+=a[i]-b[i];
}
else {
cout << "No" << '\n';
return ;
}
cout<<"Yes"<<'\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--){
slove();
}
return 0;
}
B(CF1793C)
这题本来是个蛮简单的题,但是审题没审清楚,没注意这是一个排列,一直在处理重复元素,hash,单调栈,map什么的都尝试过
很典的一个双指针,用两个变量,一个记录最大值一个记录最小值,然后双指针从左右两侧向中间靠,再讨论四种情况,不断更新左右最大值最小值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int a[N];
int n;
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int ma = n;
int mi = 1;
int l = 1, r = n;
int ansl = 0, ansr = 0;
while (r - l + 1 >= 4)
{
bool flag = 0;//判断当前区间是否满足四种情况中的一种
while (a[r] == ma && r - l + 1 >= 4)
{//右端点是最大值
r--;//右端点左移
ma--;//最大值-1
flag = 1;
}
if (r - l + 1 < 4)
break;
while (a[r] == mi && r - l + 1 >= 4)
{//右端点是最小值
r--;//右端点左移
mi++;//最小值+1
flag = 1;
}
if (r - l + 1 < 4)
break;
while (a[l] == ma && r - l + 1 >= 4)
{//左端点是最大值
l++;//左端点右移
ma--;//最大值-1
flag = 1;
}
if (r - l + 1 < 4)
break;
while (a[l] == mi && r - l + 1 >= 4)
{//左端点是最小值
mi++;//最小值+1
l++;//左端点右移
flag = 1;
}
if (!flag)
{//四种情况都没有发生
ansl = l, ansr = r;
break;
}
}
if (!ansl)
{
cout << -1 << endl;
}
else
{
cout << ansl << " " << ansr << endl;
}
}
return 0;
}
C(CF1738B)
-
先从s的后k项判断一下a的后面k-1项是不是满足递增的
-
然后数学推导一下会发现a的前n-k+1项,最大值就两种情况,s(n-k+1)/(n-k+1)或者s(n-k+1)/(n-k+1)+1,这里的/都是向下取整
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll s[200010]; void slove(){ int n,k; cin>>n>>k; ll pre=0; for(int i=1;i<=k;i++) { cin >> s[i]; } pre=s[2]-s[1]; for(int i=3;i<=k;i++){ if(s[i]-s[i-1]<pre){ cout<<"No"<<'\n'; return; } else pre=s[i]-s[i-1]; } if(k==1) { cout << "Yes" << '\n'; } else if(s[1]>=0){ if(s[1]%(n-k+1)){ if(s[1]/(n-k+1)+1<=s[2]-s[1]) cout<<"Yes"<<'\n'; else cout<<"No"<<'\n'; } else{ if(s[1]/(n-k+1)<=s[2]-s[1]) cout<<"Yes"<<'\n'; else cout<<"No"<<'\n'; } } else{ if(s[1]/(n-k+1)<=s[2]-s[1]) cout<<"Yes"<<'\n'; else cout<<"No"<<'\n'; } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin>>t; while(t--){ slove(); } return 0; }
D(CF1753A1)
又审题审错了,看成了每个si都要为0,实际上是si的和为0,1200的题都没做出。。。。。
- 如果数组长度为奇数,那不可能构造出
- 数组长度为偶数,可以发现,ai和a(i+1)若相同,则将他们组成一个区间,若不同,则将他们分为两个长度为1的区间,这样他们的s相加也为0
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
const int maxn = 200010;
int n, a[maxn];
void solve() {
cin>>n;
for (int i = 1; i <= n; ++i) {
cin>>a[i];
}
if (n & 1) {
cout<<-1<<'\n';
return;
}
vector<pii> ans;
for (int i = 1; i <= n; i += 2) {
if (a[i] == a[i+1]) {
ans.push_back({i, i + 1});
} else {
ans.push_back({i, i});
ans.push_back({i + 1, i + 1});
}
}
cout<<ans.size()<<'\n';
for (auto p: ans) {
cout<<p.first<<" "<<p.second<<'\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
int cas = 1;
while (t--) {
solve();
}
return 0;
}
E(CF1753A2)
这题是上个题的困难版本,改动的条件是a数组不止由1和-1组成,还有可能有0
和上一个题其实相差也不多,可以考虑这么构造,相邻两个相等依旧选择把他们放到一个区间,不相等就判断是不是有0,如果没有0,那就分为两个区间,有0,再往后面看一位,若这个数和原来的那个非0数相等则将三个数放入一个区间,否则三个数构成三个区间。总而言之,这里面的0是可以忽略的,那么我们把0忽略后,就变成了上面那个简单版问题
同时还看到一个更巧妙的解法:
考虑分段的本质是什么,其实就是给每个元素乘上 11 或 −1−1,不能有两个相邻的元素都乘上 −1−1。
于是考虑,初始时假设每个元素都乘上了 11,现在我们需要将其中一些元素改为乘上 −1−1,修改的两个元素不能相邻。
直接贪心地选取即可,从前往后枚举每个元素,判断若当前元素乘上 −1−1,元素和的绝对值是否会减小,若减小就操作,然后记录一下,下一个元素不能再乘上 −1−1。
贪心的正确性也很好证明,若当前元素乘上 −1−1 后,元素和的绝对值会减小,但是我们不修改当前元素,那么下一个元素最多也只会让元素和的绝对值减小相同的数,所以我们先对当前元素进行修改,一定不会更劣,就做完了。
#include <bits/stdc++.h>
using namespace std;
int wz[200005],n,a[200005],b[200005],x[200005],L[200005],R[200005];
int main(){
int t;
cin >> t;
while(t--){
int n;
cin >> n;
int cnt=0,ok=0,len=0;
for (register int i=1;i<=n;i++){
cin >> a[i];
if (a[i]!=0) b[++cnt]=a[i],wz[cnt]=i;//记录每个非0位置的信息
}
if (cnt%2==1){
cout << -1 << endl;//无解
continue ;
}
for (register int i=1;i<=cnt;i+=2){
if (b[i]!=b[i+1]){//第二种情况
for (register int j=wz[i-1]+1;j<=wz[i+1];j++){
L[++len]=R[len]=j;//每段单独分
}
}
else {
if (wz[i]+1==wz[i+1]) L[++len]=wz[i-1]+1,R[len]=wz[i+1];//相邻就直接分,注意要从上个非0位置+1开始
else {
for (register int j=wz[i-1]+1;j<=wz[i];j++) L[++len]=R[len]=j;//i之前全部单独分
for (register int j=wz[i]+1;j<=wz[i+1]-2;j++) L[++len]=R[len]=j;
L[++len]=wz[i+1]-1,R[len]=wz[i+1];//给第i+1个非零位置前留下一个0使它在区间里的长度变为偶数
}
}
}
if (R[len]<n){
L[len+1]=R[len]+1,R[len+1]=n;
len++;//可能最后一个非零数后面还有,单独再开一段
}
cout << len << '\n';
for (register int i=1;i<=len;i++) cout << L[i] << ' ' << R[i] << endl;
}
}
#include<bits/stdc++.h>
# define sum(x,y) (s[y]-s[x-1])
using namespace std;
inline int rd(){
int f=1,s=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){s=(s<<3)+(s<<1)+(c^48);c=getchar();}
return s*f;
}
const int N = 2e5;
int t,n,s[N+5],f[N+5],g[N+5],a[N+5];
int main(){
t=rd();
while(t--){
n=rd();int sum=0,cnt=n;
for(int i=1;i<=n;++i)
a[i]=rd(),sum+=a[i],g[i]=0;
for(int i=2;i<=n;++i)
if(abs(sum-2*a[i])<abs(sum)&&!g[i-1])
g[i]=1,--cnt,sum-=2*a[i];
if(sum)printf("-1\n");
else{
printf("%d\n",cnt);
g[n+1]=0;
for(int i=1;i<=n;++i){
if(g[i])continue;
if(g[i+1])printf("%d %d\n",i,i+1);
else printf("%d %d\n",i,i);
}
}
}
return 0;
}
F(CF1761C)
一个图论的构造题,先建图把优先级确定,让真子集指向父集合,初始可以把集合1到n初始值都赋为自己的下标i,如集合3初始值3,这样就能避免重复了,接着这就是一个标准的拓扑排序的题了,只不过在拓扑排序的过程中我们要进行集合加法,这个用set可以很容易解决
#include<iostream>
#include<queue>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 110;
int ind[maxn];
void slove() {
int n, x;
cin >> n;
vector<int>g[maxn];
vector<set<int>>ans(maxn);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
scanf("%1d", &x);
if (x)
ind[j]++, g[i].push_back(j);
}
for(int i=1;i<=n;i++)
ans[i].insert(i);
queue<int>q;
for (int i = 1; i <= n; i++)
if (ind[i]==0)
q.push(i);
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < g[u].size(); i++) {
if ((--ind[g[u][i]]) == 0) {
q.push(g[u][i]);
}
for(auto it:ans[u])
ans[g[u][i]].insert(it);
}
}
for (int i = 1; i <= n; i++) {
cout<<ans[i].size();
for(auto it:ans[i])
cout<<" "<<it;
cout<<endl;
}
for (int i = 1; i <= n; i++)
ind[i] = 0;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--) {
slove();
}
return 0;
}
G(CF1788C)
构造方法:根据等差数列求和公式可以判断构造是否存在,以及第一项的值a,接着第一项选取1和a-1,然后选取3,a-1,也就是第一项加2,第二项-1,这样就可以等差数列的条件了,当第一个数大于等于第二个数时停止上诉操作,然后选取第一项为2,重复第一项+2,第二项-1的操作,可以数学证明,能够取完所有的数
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
typedef long long ll;
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--)
{
ll n;
cin >> n;
if (n & 1)
{
cout << "YES" << endl;
ll b = 2 * n - n / 2;//s/n-n/2-1
ll a = 1;
for (int i = 1; i <= (n + 1) / 2; i++)
{//遍历n范围内的所有奇数,构成第1个到中间的数对
cout << a << " " << b << endl;
a += 2;
b--;
}
a = 2, b = 2 * n;//2和s/n+1-2
for (int i = 1; i <= n / 2; i++)
{//遍历n范围内所有偶数
cout << a << " " << b << endl;
a += 2;
b--;
}
}
else
{
cout << "NO" << endl;
}
}
return 0;
}
H(CF1741D)
因为只能交换子树,所以可以从叶子节点往上走,每次都更新子树中的数的左右边界,如果两个子树的区间出现了交叉,那么必然不可以通过交换满足题目要求,反之就判断谁在前谁在后,也就是是否需要交换
#include<iostream>
#include<queue>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
struct {
int l=INT_MAX,r=0;
}tree[maxn*2];
int m,ans=0,flag=0;
void dfs(int u){
if(u>=m||flag)
return;
dfs(u*2);
dfs(u*2+1);
if(tree[u*2].l<=tree[u*2+1].r&&tree[u*2].r>=tree[u*2+1].l||tree[u*2+1].l<=tree[u*2].r&&tree[u*2+1].r>=tree[u*2].l)
flag=1;
else if(tree[u*2].l>=tree[u*2+1].r)
ans++;
}
void slove(){
cin >> m;
for(int i=m;i<2*m;i++)
cin>>tree[i].l,tree[i].r=tree[i].l;
for(int i=m;i<2*m;i+=2){
int a=i/2;
while(a){
tree[a].l=min(tree[a*2].l,tree[a*2+1].l),
tree[a].r=max(tree[a*2].r,tree[a*2+1].r);
a/=2;
}
}
dfs(1);
if(flag)
cout<<-1<<'\n';
else
cout<<ans<<'\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--) {
slove();
flag=0;
ans=0;
}
return 0;
}