CF1523B Lord of the Values
题意
t 组数据,每组数据给一个数组长度 n,以及数组 a。要求通过以下两个操作使得 a 数组中的元素变为原来的相反数并输出方案。
思路
- 条件给出n为偶数,提出能否两个两个数进行操作
- 通过两种操作推出步骤
- 例如:(a,b)->(a+b,b)->(a+2b,b)->(a+2b,-a-b)->(b,-a-b)->(-a,-a-b)->(-a,-b)
- 每取两个得出固定规律,循环n/2次
坑点
- 关注特殊值n,n为偶数
实现步骤
- 确定循环的次数,n/2的值
- 确定每次循环的输出
代码
#include<iostream>
#include<stdio.h>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int long long int
using namespace std;
const int N=1e5+10;
int a[N];
signed main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
int s=n/2;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
cout<<s*6<<endl;
for(int k=1;k<=n;k+=2)
{
cout<<"1 "<<k<<" "<<k+1<<endl;
cout<<"1 "<<k<<" "<<k+1<<endl;
cout<<"2 "<<k<<" "<<k+1<<endl;
cout<<"1 "<<k<<" "<<k+1<<endl;
cout<<"1 "<<k<<" "<<k+1<<endl;
cout<<"2 "<<k<<" "<<k+1<<endl;
}
}
return 0;
}
总结
规律思维题
CF1374C Move Brackets
题意
给定一个长为 n 的括号序列;
每次你可以选择任意一个括号,将其移至序列最左侧或最右侧;
保证可能使括号序列合法,求使括号序列合法的最少操作次数。
思路
- 判断括号的合理性需要合理的配对
- 当左括号未出现右括号已经出现就是需要移动
- 从左往右依次遍历判断括号的合理性
坑点
- 无
实现步骤
- 从左往右依次遍历括号
- 当左括号出现加1,右括号出现减1
- 当为-1时就是需要通过移动来解决,同时答案加1后要统计数要变为0
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
string s;
cin>>s;
int sum=0,ans=0;//sum计数判断移动情况,ans记录移动次数
for(int i=0;i<n;i++)
{
if(s[i]=='(')
{
sum++;
}
else if(s[i]==')')
{
sum--;
}
if(sum==-1)
{
ans++;
sum=0;
}
}
cout<<ans<<endl;
}
return 0;
}
总结
判断括号的合理性
CF1551C Interesting Story
题意
给定 n个仅由a,b,c,d,e 组成的单词 (n≤2×10^5),从其中选出尽可能多的单词,使得存在某个字母在这些单词中出现的次数比其他所有字母的出现次数之和还要多。输出最多能选的单词个数。若无法构造,输出 0
思路
- 尽可能的用多的单词组成,使得存在某个字母在这些单词中出现的次数比其他所有字母的出现次数之和还要多。就是数量大于一半。
- 依次遍历每个字母出现次数与其他字母出现次数的差,将差值从大到小排序。
- 将排序的进行相加,当总和小于等于0说明不能达到一半多,取出最大个数,每个字母遍历进行比较。
坑点
- 不仅仅达到一半,还要超出一半
实现步骤
- 遍历每个字母与其他字母的差
- 统计每个字母的数量和
- 每个字母与其他字母的差 从大到小排序
- 当总值小于等于0不成立
代码
#include<iostream>
#include<stdio.h>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int long long int
using namespace std;
const int N=2e5+10;
string s[N];
int d[N];
signed main()
{
int t,n;
cin>>t;
while(t--)
{
int ans=0;//记录最后答案
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i];//输入n个单词
}
for(char r='a';r<='e';r++)//遍历每个字母与其他字母的差
{
for(int j=1;j<=n;j++)
{
int sum=0;
int x=s[j].length();//总长度
for(int k=0;k<x;k++)
{
if(s[j][k]==r)//统计每个字母的数量和
{
sum++;
}
}
d[j]=sum-(x-sum);//每个字母与其他字母的差
}
sort(d+1,d+1+n,greater<int>());//每个字母与其他字母的差 从大到小排序
int num=0,num1=0;
for(int k=1;k<=n;k++)
{
if(num+d[k]<=0)//当总值小于等于0不成立
{
break;
}
num1++;
num+=d[k];
}
ans=max(num1,ans);
}
cout<<ans<<endl;
}
return 0;
}
总结
直接模拟太长,可转换成先算单个单词里的数量差,再进行比较求值。
CF1684C Column Swapping
题意
如果一个表格每行都单调不降,称它为好的。
给你 t 个 n_i行 m_i列的表格,对于每个表格,询问是否能通过调换某两列 (不一定不同) 使得这个表格是好的(这样的操作需要且仅能执行一次)。如果可以,输出两列的编号;不可以,输出 -1。
思路
- 判断是否需要移动,如果不需要移动输出1,1
- 如果需要移动,判断如果确定id行修改需要修改的点数的个数如果大于2个不可修改
- 将确定的两列 x , y 进行改动判断是否符合条件,符合条件输出 x , y ,反之输出-1
坑点
- 内存超限不用二维数组,用vector
- 当确定两列进行调整后判断每行列是否都符合条件
实现步骤
- 判断原始的数据是否需要改动,如果不需要改变直接输出
- 判断如果确定id行修改需要修改的点数的个数如果大于2个不可修改,确定需要改变的两列
- 将确定的两列进行改动判断是否符合条件,决定输出
代码
#include<iostream>
#include<stdio.h>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int long long int
using namespace std;
const int N=2e5+10;
int n,m,x,y;
vector<int>v[N];//利用vector减少内存量
bool check(int id)//判断如果确定id行修改需要修改的点数的个数如果大于2个不可修改
{
int a[N];
for(int i=1;i<=m;i++)
{
a[i]=v[id][i];
}
sort(a+1,a+1+m);//按正确顺序排序
int num=0;
for(int i=1;i<=m;i++)
{
if(a[i]!=v[id][i])
{
num++;
if(num==1)
{
x=i;
}
if(num==2)
{
y=i;
}
}
}
return num<=2;
}
signed main()
{
int t;
cin>>t;
while(t--)
{
int f1=0;//判断是否需要改动
cin>>n>>m;
for(int i=1;i<=n;i++)
{
v[i].clear();
v[i].push_back(-999);//行数从1开始填充
for(int j=1;j<=m;j++)
{
int x;
cin>>x;
v[i].push_back(x);
if(v[i][j]<v[i][j-1])//判断是否为不单调降
{
f1=1;
}
}
}
if(f1==0)//如果不需要改变直接输出
{
cout<<"1 1"<<endl;
continue;
}
int f=0;//判断如果需要改变的情况
for(int i=1;i<=n;i++)
{
if(check(i)==0)
{
f=1;
cout<<"-1"<<endl;
break;
}
}
if(f==0)
{
for(int i=1;i<=n;i++)//将确定的两列进行改动判断是否符合条件
{
swap(v[i][x],v[i][y]);
}
f1=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(v[i][j]<v[i][j-1])
{
f1=1;
}
}
}
if(f1==0)
{
cout<<x<<" "<<y<<endl;
}
else
{
cout<<"-1"<<endl;
}
}
}
return 0;
}
总结
思维题排序+模拟
CF1558A Charmed by the Game
题意
Alice 和 Borys 在打网球。网球比赛有很多局,每局有一个人发球,有一个人接发球。这样轮流交换,一人一局。每局比赛都有一个胜者,如果胜者是发球者,那么称为保发,反之称为破发。我们知道 Alice 赢了 a 局,Borys 赢了 b 局,但我们不知道谁先发球和每局谁赢了。
问所有可能的总破发次数。
思路
- 根据规律推出公式
- 提出假设,求出所有可能的总破发数
- 利用set去重
坑点
- 要升序输出
- 注意去重
实现步骤
- 利用set去重排序
- 公式推出A,B的发球次数,枚举A发球赢的次数 (保发)
- 提出两条件A赢的次数-A发球赢的次数=A接球赢的次数(破发)==B发球输的次数<= B的发球次数 ;A发球赢的次数<=A发球的次数
- A接球赢的次数+A发球输的次数 (B接球赢的次数)
代码
#include<iostream>
#include<stdio.h>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int long long int
using namespace std;
signed main()
{
int t;
cin>>t;
while(t--)
{
set<int>st;//统计去重
int a,b;
cin>>a>>b;
int x=(a+b)/2;//A的发球次数
int y=a+b-x;//B的发球次数
for(int i=0;i<=a;i++)//枚举A发球赢的次数 (保发)
{
//A赢的次数-A发球赢的次数=A接球赢的次数(破发)==B发球输的次数<= B的发球次数
//A发球赢的次数<=A发球的次数
if(a-i<=y&&x>=i)
{
//A接球赢的次数+A发球输的次数 (B接球赢的次数)
int f=a-i+x-i;
st.insert(f);
}
}
swap(a,b);
x=(a+b)/2;
y=a+b-x;
for(int i=0;i<=a;i++)
{
if(a-i<=y&&x>=i)
{
int f=a-i+x-i;
st.insert(f);
}
}
cout<<st.size()<<endl;
for(auto i:st)
{
cout<<i<<" ";
}
cout<<endl;
}
return 0;
}
总结
利用假设推公式
CF1703F Yet Another Problem About Pairs Satisfying an Inequality
题意
给你一个序列a_1, a_2, …a n 。请计算出满足下面条件的 (i,j) (1≤i,j≤n)个数 。
思路
- 利用两重循环必然超时,将条件分解成 a i < i a_i<i ai<i, i < a j i<a_j i<aj进行判断,先将条件一 a i < i a_i<i ai<i的数据放入数组,和结构体中
- 利用二分进行时间的压缩,将位置按从小到大排,再将值按从小到大排,对位置进行单独判断。
- 利用循环将在值排序中第一个大于当前位置的数的位置找到,就是寻找满足第二条件的。
坑点
- 时间限制
- 每次的数组与结构体进行迭代更新
实现步骤
- 判断是否符合第一条件存入数组与结构体中 。
- 按位置大小排序 ,按值的大小排序 。
- 找到第一个大于id(当前位置)的值 ,判断是否可以找到,统计答案,最后输出。
代码
#include<iostream>
#include<stdio.h>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int long long int
using namespace std;
const int N=4e5+10;
int a[N];
struct name{
int q;//值
int id;//位置
}b[N];
int c[N];//用于值大小的排序
bool cmp(name a,name b)//按照位置从小到大排序
{
return a.id<b.id;
}
signed main()
{
int t,n;
cin>>t;
while(t--)
{
cin>>n;
int ans=0;//记录答案
int num=1;//记录数据数量
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]<i)//判断是否符合第一条件存入数组与结构体中
{
b[num].q=a[i];
b[num].id=i;
c[num]=a[i];
num++;
}
}
num--;
sort(b+1,b+1+num,cmp);//按位置大小排序
sort(c+1,c+1+num);//按值的大小排序
for(int i=1;i<=num;i++)
{
int p=upper_bound(c+1,c+1+num,b[i].id)-c;//找到第一个大于id(当前位置)的值
if(p!=num+1)//判断是否可以找到
{
int l=num-p+1;//计算有几个数可以满足
ans+=l;//统计答案
}
else{
break;
}
}
cout<<ans<<endl;
}
return 0;
}
总结
利用二分等方法进行时间压缩
CF1729C Jumping on Tiles
题意
给定一个字符串 s,polycarp 欲从字符串首跳到字符串末 (s_1 → s_n,其中 n 表示该字符串长度)。假设 polycarp 现从 a_i跳到了 a_j我们定义这一次跳跃的权值为 |{index}(a_i) - {index}(a_j)|,其中 index 表示该字符在字母表中的序号 ( 如 {index}(‘a’) = 1, …{index}(‘z’) = 26)
请构造出一种在保证权值和最小的情况下经过的字符最多的跳跃方案 ( 当然,同一个字符只能经过一次,其中同一个仅指在字符串中的位置相同 )。
思路
- 确定是从1->n,要权值最小就是为 ∣ s 3 − s 1 ∣ |s_3-s_1| ∣s3−s1∣
- 要使经过的字符最多,当跳的字符为单调性,最后得到的值就固定为 ∣ s 3 − s 1 ∣ |s_3-s_1| ∣s3−s1∣
- 先判断首尾的大小,将成立的字符依次排入,满足跳过的字符最多
坑点
- 无
实现步骤
- 先判断首尾字符值的大小,将所有的进一步排序
- 在排好的序列中找到第一位,从第一位遍历计算最后一共能跳多少个字符
- 将位置按顺序依次输出
代码
#include<iostream>
#include<stdio.h>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int long long int
using namespace std;
const int N=2e5+10;
struct name{
int w;//位置
int q;//权值
}a[N];
bool cmp(name x,name y)//从大到小排序
{
if(x.q !=y.q)
{
return x.q > y.q;
}
return x.w<y.w;
}
bool cmp1(name x,name y)//从小到大排
{
if(x.q !=y.q)
{
return x.q < y.q;
}
return x.w<y.w;
}
signed main()
{
int t;
cin>>t;
while(t--)
{
string s;
cin>>s;
int len=s.length();
int s1=s[0]-'a'+1;
int s2=s[len-1]-'a'+1;
for(int i=0;i<len;i++)
{
int x=s[i]-'a'+1;
a[i+1].w=i+1;
a[i+1].q=x;
}
if(s1<s2)
{
sort(a+1,a+1+len,cmp1);
}
else{
sort(a+1,a+1+len,cmp);//从大到小排
}
int d;
for(int i=1;i<=len;i++)//找到第一个字符
{
if(a[i].w==1)
{
d=i;
break;
}
}
int sum=0;
for(int i=d;i<=len;i++)
{
sum++;
if(a[i].w==len)
{
break;
}
}
cout<<abs(s1-s2)<<" "<<sum<<endl;
for(int i=d;;i++)
{
cout<<a[i].w<<" ";
if(a[i].w==len)
{
break;
}
}
cout<<endl;
}
return 0;
}
总结
先找到最小权值的方法,确定字符数的最多