题意:输入一个仅含小写字母的字符串,请问最少删除多少个字母使得,剩下的字符串中含有成对的相同字母,如“ababa”,在进行三步后有“bb”或“aa”为最少步骤,“asdfa”在三步后有“aa”为最少步骤。
思路:
本菜菜在冥思苦想半个小时有了dp这个思路,先记录字符串中每个字母的数量,因为单个字母是一定要删的,所以先用两个O(n)删去单个字符,同时记录个数,然后想,在字符串从前往后遍历的时候,用数组记录从头到此位置要达到题目要求的最少次数,而我们每次要进行操作的话,就要把两个相同字母之间的字母都删了此时次数为dp [ i ] = dp [ j - 1 ] + i - j + 1,(i是此时的位置,j是前面相同字母的位置),但是如果此位置字母前面没有相同字母,dp [ i ] = dp [ i - 1 ] + 1,就是把此位置的字母当成要删的加进去,当然要给dp数组的头初始化,dp [ 0 ] =1,还有首字符的位置要记录为0,同时在往后遍历的同时要判断是否与首字符相同且该字符的前一个位置,这是特殊情况
#include<bits/stdc++.h>
using namespace std;
map< char ,int > p,m; //p记录目前每个字符的个数,m记录前一个与当前字符相同的位置
int dp[200010];
int main()
{
string s;
int T,n,x;
cin>>T;
while(T--){
int step=0;
s.clear(),p.clear(),m.clear(); //初始化
cin>>s;
int len=s.length();
for(int i=0;i<len;++i) //两个for循环清除只有一个的字符
p[s[i]]++;
for(int i=0;i<len;){
if(p[s[i]]==1){
s.erase(i,1); //删除只有一个的字符
len--;
step++; //记录操作次数
}else i++;
}
memset(dp,0,len*sizeof(int));
m[s[0]]=0,dp[0]=1; //dp和首字符位置初始化
for(int i=1;i<len;++i){
if(m[s[i]]==0&&s[i]==s[0])dp[i]=min(dp[i-1]+1,i-1); //当前与首字符相同,且是第二个首字母
else if(m[s[i]]==0)dp[i]=dp[i-1]+1; //当前字符第一次出现时
else dp[i]=min(dp[i-1]+1,dp[m[s[i]]-1]+i-m[s[i]]-1);
m[s[i]]=i; //更新该位置字符的最新下标
}
cout<<step+dp[len-1]<<endl;
}
}
虽然是之后ac的,但也是我努力做出来的一道dp呜呜呜,(其实这道题可以用贪心做,在找到一对相同的字符时就记录这对,然后再初始化所有字符数量,再往后,因为贪心目的是把一对字符中间最少的删除,所以每找到一对就要清空计数,代码如下)
#include<bits/stdc++.h>
using namespace std;
int main(){
int T;
cin>>T;
while(T--){
map<char,int> p;
p.clear();
string s;
int num=0;
cin>>s;
int len=s.length();
for(int i=0;i<len;++i){
if(p[s[i]]==1)
num+=2,p.clear(); //记录已配对的个数,清空计数
else p[s[i]]++;
}
cout<<len-num<<endl;
}
}
D:Maximum Product Strikes Back
题意:给定一个数组,对每个a[i],范围是【-2,2】的整数,求从数组头和数组尾分别向数组中间删除多少个数使得剩余每个数相乘的乘积最大
题解:若要乘积最大,而每个元素的范围的绝对值就是0到2,如果有0参与乘法的话乘积必为0,题目还告诉我们如果数组的长度为0,那么结果是1,所以在数组长度不为0的情况下,最大乘积应>=1,所以我们可以用0来把数组分为几个数组段,同时更新乘积最大时的初始位置和结束位置。
对于每个数组段内含有正负数,我们可以用含2和-2的个数number_two来比较乘积的大小,同时还要记录乘积的正负号flag,当往后遍历遇到第一个负数时,记录下该负数下一个数的下标,和更新这一段到该负数含2或-2个数的个数first_to_nega。
当此时乘积为正且2或-2的个数最大时更新始末位置,若乘积为负且在第一个负数之后时,比较第一个负数到此位置的2或-2的个数number_two-first_to_nega与first_to_nega的大小,若前者大则更新最优始末位置
这道题搞了我一天,我真是菜鸡害
#include<bits/stdc++.h>
using namespace std;
int T,n,a[200010];
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int l=1,maxx=0,left=1,right=0,number_two=0,flag=1,first_to_nega=-1,fl=1;
//fl记录这一段的初始位置,maxx是2或-2的个数,left和right分别记录最大乘积范围的位置
//number_two记录这一段中2或-2的个数,flag判断一组数相乘时结果是否为负
//first_to_nega记录一段开头至第一个负数之间的2或-2的个数
//f1记录第一个负数后面的下标
for(int i=1;i<=n;i++){
if(a[i]==0){
l=i+1;
number_two=0; //number_two记录每一段中含有2的数量
first_to_nega=-1,flag=1; //first_to_nega记录一段的起点到第一个符号间2的数量,flag记录这一段起点乘到当前是否为负
}
if(a[i]==-1)
flag*=-1;
else if(a[i]==2)
number_two++;
else if(a[i]==-2){
number_two++;
flag*=-1;
}
if(a[i]<0&&first_to_nega==-1)first_to_nega=number_two,fl=i+1; //此时为这一段的第一个负数,first_to_nega记录这一段已有的2的数量
//f1为临时记录一段非0连续第一个符号后的首位置
if(flag==1&&number_two>maxx)
maxx=number_two,left=l,right=i;
if(flag!=1&&first_to_nega!=-1&&number_two-first_to_nega>maxx) //当前为连续几个负号后且,此时乘积为负,且当前到第一个负号之间2的数量比第一个负号之前的2数量多
maxx=number_two-first_to_nega,left=fl,right=i;
}
cout<<left-1<<' '<<n-right<<endl;
}
}