题意: 给你一个长度为 n n n的 a a a数组,你每次操作可以进行下述两种操作的任意一种,求最小操作数使 a a a数组所有数变成 0 0 0:
思路: 分类讨论,如果存在 0 0 0,那么最小操作数即非 0 0 0的个数。 如果不存在 0 0 0,并且存在相同数那么最小操作数即所有数的个数,因为可以通过 1 1 1次操作将 1 1 1个数变成 0 0 0。否则我们需要再多 1 1 1次操作,先将 2 2 2个数变成相同数。
代码:
#include
using namespace std;
#define int long long
void cf(){
int n;
cin>>n;
map<int,int>mp;
bool ok =false;
bool ok2=false;
for(int i=1;i<=n;i++){
int x;
cin>>x;
if(x==0)ok=true;
mp[x]++;
if(mp[x]>1)ok2=true;
}
if(ok){
int res=0;
for(auto x:mp){
if(x.first!=0)res+=x.second;
}
cout<<res<<endl;
return ;
}
int res=0;
for(auto x:mp){
res+=x.second;
}
if(!ok2)res++;
cout<<res<<endl;
return ;
}
signed main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
while(t--){
cf();
}
}
题意: 给定偶数长度的字符串,其中包含多组由仅由 0 0 0或 1 1 1组成的字符串,我们每次操作可以使其中一个 1 1 1变成 0 0 0或者 0 0 0变成 1 1 1。问最小的操作次数使得每组由单一 0 0 0或 1 1 1组成的字符串长度皆为偶数。
思路: 因为要求每组长度皆为偶数,那么如果将字符串中的字符 2 2 2个 2 2 2个分组,意味着每组中的 2 2 2个字符必须为同一类型,所以如果 2 2 2个字符不是同一类型操作次数就要 + 1 +1 +1。
代码:
#include
using namespace std;
#define int long long
void cf(){
int n;
cin>>n;
string s;
cin>>s;
int res=0;
for(int i=0;i<s.size();i+=2){
res+=(s[i]!=s[i+1]?1:0);
}
cout<<res<<endl;
}
signed main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
while(t--){
cf();
}
}
题意: 在B1基础上,额外要求输出最小操作前提下,最少组的数量。
思路: 首先观察字符串中 01 01 01和 10 10 10变化对整体的影响, 01 01 01和 10 10 10皆可以通过 1 1 1次操作变成 11 11 11或 00 00 00的形式,这说明不管其左边或右边是什么字符, 10 10 10和 01 01 01形式的子串皆可以通过一次操作完美的与左边或右边的字符融为一体,换句话说就是隐身掉了。那么问题自然就转变成了统计删去 01 01 01、 10 10 10之后,有多少组连续 0 0 0和连续 1 1 1子串。
代码:
#include
using namespace std;
#define int long long
const int N = 2e5+10;
bool st[N];
void cf(){
int n;
cin>>n;
string s;
cin>>s;
for(int i=0;i<s.size();i++)st[i]=false;
int a,b;
a=b=0;
for(int i=0;i<s.size();i+=2){
if(s[i]!=s[i+1]){
a++;
st[i]=st[i+1]=true;
}
}
int pre=-1;
for(int i=0;i<s.size();i++){
if(!st[i]){
if(pre==-1){
b++;
pre=s[i]-'0';
}
else if(pre!=s[i]-'0'){
b++;
pre=s[i]-'0';
}
}
}
if(pre==-1)b++;
cout<<a<<' '<<b<<endl;
return ;
}
signed main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
while(t--){
cf();
}
}
题意: 给定一个长度不超过 5000 5000 5000的排列数组 p p p,统计其中有多少个四元组 [ a , b , c , d ] [a,b,c,d] [a,b,c,d]满足 a < b < c < d aa<b<c<d且 p [ c ] > p [ a ] 且 p [ b ] > p [ d ] p[c]>p[a]且p[b]>p[d] p[c]>p[a]且p[b]>p[d]。
思路: 看到 5000 5000 5000我们会往 O ( n 2 ) O(n^2) O(n2)复杂度的实现方法上去思考,观察后发现我们可以枚举 b b b和 c c c的位置,因为一旦 b b b和 c c c的位置确定下来,我们就可以确定查找范围,即在 1 1 1 ~ b − 1 b-1 b−1的区间内找比 p [ c ] p[c] p[c]小的数量,在 c + 1 c+1 c+1 ~ n n n的区间内找比 p [ b ] p[b] p[b]小的数量。但是我们已经用了 O ( n 2 ) O(n^2) O(n2)复杂度了,显然里面剩下的查询操作 O ( 1 ) O(1) O(1)比较合理,此时就要先将查询的资料预处理。开一个 p r e [ 5000 ] [ 5000 ] pre[5000][5000] pre[5000][5000]的数组用 O ( n 2 ) O(n^2) O(n2)的时间复杂度创建,第 1 1 1维表示当前枚举到的位置,第 2 2 2维表示 1 1 1一个数,即 p r e [ 5 ] [ 8 ] pre[5][8] pre[5][8]表示的含义是前面 5 5 5个数中比 8 8 8小的数的个数,预处理完 O ( 1 ) O(1) O(1)查询既可以解决该题。
代码:
#include
using namespace std;
#define int long long
const int N = 5010;
int pre[N][N];
int a[N];
void cf(){
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)pre[i][j]=0;
for(int i=1;i<=n;i++){//预处理
for(int j=1;j<=n;j++){//小于等于j的前i个数
pre[i][j]=pre[i-1][j];
}
for(int j=a[i];j<=n;j++)pre[i][j]++;
}
int res=0;
for(int b=2;b<=n-2;b++)//查询统计
for(int c=b+1;c<=n-1;c++){
res+=pre[b-1][a[c]-1]*(pre[n][a[b]]-pre[c][a[b]]);
}
cout<<res<<endl;
}
signed main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
while(t--){
cf();
}
}
题意: 现在 n ∗ m n*m n∗m 名学生将按以下方式就坐:
思路: 将行和列拆开来讨论,首先看列: 在纸上画图,每当进入一个新学生,老学生的移动情况为从 1 1 1 ~ n − 1 n-1 n−1 列移动到 2 2 2 ~ n n n列,这给我们一个信息,所有 c o l [ i col[i col[i% m ] 、 m]、 m]、 c o l [ ( i + m ) col[(i+m) col[(i+m)% m ] . . . c o l [ ( i + k ∗ m ) m]...col[(i+k*m) m]...col[(i+k∗m)% m ] m] m]始终在一根线上也就是一列上,不管后面加了多少新学生。所以如果 c o l [ i col[i col[i% m ] m] m]的值是 1 1 1,那么后面出现的同在这根线上的学生值一定是 1 1 1,即学生 i + m 、 i + 2 ∗ m . . . i+m、i+2*m... i+m、i+2∗m...。再次看行: 每新增 m m m 名学生,所有学生的位置下移一行,所以行的贡献将增加以下的数值:这 m m m 名学生将占据第一行,若其中有 1 1 1 ,则带来 1 的贡献。否则没有额外贡献。提前计算好前缀和,就能 O ( 1 ) O(1) O(1) 地求出它有没有带来新的贡献。
代码:
#include
using namespace std;
#define int long long
void cf(){
int n,m;
cin>>n>>m;
string s;
cin>>s;
vector<int>pre(n*m,0);
//计算列
vector<int>a(n*m,0);
vector<int>col(m,0);
int ti=0;
for(int i=0;i<n*m;i++){
int x=s[i]-'0';
if(x==1&&!col[i%m]){
col[i%m]=1;
ti++;
}
a[i]=ti;
}
//计算行
pre[0]=0;
for(int i=1;i<s.size();i++){
pre[i]+=pre[i-1]+s[i]-'0';
}
ti=0;
for(int i=0;i<m;i++){
ti|=s[i]-'0';
int can=ti;
a[i]+=ti;
for(int j=i+m;j<n*m;j+=m){
if(pre[j]-pre[j-m]>0)can++;
a[j]+=can;
}
}
for(auto x:a)cout<<x<<' ';
cout<<endl;
return ;
}
signed main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
while(t--){
cf();
}
}
题意: 给定两个排列 a a a和 b b b,自由将 1 1 1~ n n n的排列数填充到 a , b a,b a,b之中要求满足同一数必须填补到 a , b a,b a,b的同色块中。求最大的 ∑ i = 1 n a b s ( a i − b i ) \sum\limits_{i=1}^nabs(a_i-b_i) i=1∑nabs(ai−bi)。
思路: 感觉和上一场的 C C C很像,导致理解起来不是很难,本质还是找环,统计每个环的长度 / 2 /2 /2的和,然后每组剩余最大最小分配即可。因为如果奇数情况,最多只有 / 2 /2 /2组可以配对。感觉不是讲的很清楚, 这个构造在本上画下图其实相对会比较好理解。找环的话我还是和上次一样做法,并查集也可,我用的是映射搜索。
代码:
#include
using namespace std;
#define int long long
const int N = 1e5+10;
int a[N],b[N];
int aa[N];
bool ok[N];
int len;
void cf(){
int n;
cin>>n;
for(int i=1;i<=n;i++)ok[i]=false;
for(int i=1;i<=n;i++)cin>>a[i],aa[a[i]]=i;
for(int i=1;i<=n;i++)cin>>b[i];
int res=0;
for(int i=1;i<=n;i++){
if(a[i]!=b[i]&&!ok[i]){
len=1;
ok[i]=true;
int j=b[i];
while(j!=a[i]){
int idx=aa[j];
ok[idx]=true;
len++;
j=b[idx];
}
res+=len/2;
}
}
int sum=0;
for(int i=1,j=n;i<=res;i++,j--){
sum+=(j-i)*2;
}
cout<<sum<<endl;
}
signed main(){
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
while(t--){
cf();
}
}