题目描述
小A打算开始炼 NOI 元丹(什么鬼),据说吃了可以提高 NOI 时的成绩。
是这么练的。元丹有三种元核,N
、O
、I
。现有很多个这样原核,按顺序排成一行。炼元丹时,从左往右分别挑出 N
、O
、I
三个原核吞下。
现在他关心,有几种服用方式……且慢!
他觉得服用方式太少,以至于不能成仙。所以他可以通过某个途径,得到 N
、O
、I
的三种原核中的任意一个,至于哪一种由他决定。然后他将获得这个原核的插入到这一排原核中的任意位置(包括最前最后)。
现在你要知道,新的元核序列中能有多少种 N
、O
、I
的取出方式。子串的字母并不要求连续。
输入格式
第一行,一个整数 N,表示字符串的长度。
第二行,一行字符串,里面只有只有 N
、O
、I
三种字母。
输出格式
表示出最多可以提炼出来的 NOI 元丹的方案种数。
输入输出样例
输入 #1
5 NOIOI
输出 #1
6
说明/提示
样例解释:
他可以获取一个 N
元核,加到最前面。
NNOIOI | NNOIOI | NNOIOI | NNOIOI | NNOIOI | NNOIOI
~ ~~ | ~ ~ ~ | ~ ~~ | ~~~ | ~~ ~ | ~ ~~
对于 30% 的数据 N≤200。
对于 50% 的数据 N≤2000。
对于 100% 的数据 3≤N≤10^5。
——————————————————我是分割线———————————————————
解题思路
我也想要NOI元丹:)
首先,我们先不看可以加上的那一个,假设输入
NNOII
那有多少种组合方式呢?
非常简单,下面用1~5来表示每个字母的编号:
134 135 234 235
一共4种,我们不难发现,如果确定一个O作为NOI元丹中的O,那么这个O的所有组合数=左边N的数量*右边I的数量,同样,如果有多个O,那么将所有O的组合数加起来就行了,一定不会重复。
那么可能有人会问,为什么要选O,而不选择N或I呢?
我们来一个样例试一下
NIOIO
选择第一个N,做边有2个O,2个I,个数为2*2=4,貌似没有错,可真的对吗?
很容易,一看就不是对的。
显然,O必须在I的前面,而后面有I的O只有一个,前面有O的I也只有一个,所以正确答案应该是1*1=1
(I同理)
现在我们知道了公式,就需要想一下另一个要求了——添加任意一个。
如果添加N,肯定添加在最左边,I肯定添加在最右边,因为着一定是最优解,在找完左右NI后将N或I +1再乘就求完了
如果添加O,那么应该添加在N和I相差最少的一个地方,至于为什么,我举个例子。
4+6=10,5+5=10
4*6=24,5*5=25
24<25
如果不懂的可以在网上查一下,这里就不再过多解释了。
解题方法
思路有了,但是该怎么写呢?
n<=1e5,如果求左右的NI暴力枚举肯定爆炸,有个简单的方法,用两个计数变量来计算(N从左到右,I从右到左),如果是N或I,则相对应的计数变量+1,如果是O,就存储。
代码
#include<iostream>
#include<cstdio>
#include<string>
#include<cmath>
#define ll long long
using namespace std;
int N[100005],I[100005];
// 用来存储第i个数左边有多少N,右边有多少I
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
int n;
scanf("%d",&n);
string a;
cin>>a;
int nn=0,ii=0;
for(int i=0;i<n;i++){
N[i]=nn;
if(a[i]=='N')nn++;
}for(int i=n-1;i>=0;i--){
I[i]=ii;
if(a[i]=='I')ii++;
}//前文说的是如果为O才存储,这里为了省事,直接赋值。
int ansn=0,anso=0,ansi=0;
int minn=0x7fffffff,j;
// ansn表示添加N,anso表示添加O,ansi表示添加I。
//minn和j用来寻找添加O的位置
for(int i=0;i<n;i++){
if(a[i]=='O'){
ansn+=(N[i]+1)*I[i];
ansi+=N[i]*(I[i]+1);
//计算
if(abs(N[i]-I[i])<=minn){
minn=abs(N[i]-I[i]);
j=i;
//如果相差的更少就更新,找到相差最少的一个加O
}
}
}for(int i=0;i<n;i++){
if(a[i]=='O'){
if(i==j)anso+=N[i]*I[i];
anso+=N[i]*I[i];
//如果这个地方是相差最少的就*2
}
}cout<<max(max(ansn,ansi),anso);
//输出
return 0;
//fclose(stdin);
//fclose(stdout);
}
70分,很严重的问题
三年OI一场空,不开longlong见祖宗
因为三个ans在计算的时候可能会炸,所以开个longlong就行了
正解:
#include<iostream>
#include<cstdio>
#include<string>
#include<cmath>
#define ll long long
using namespace std;
int N[100005],I[100005];
// 用来存储第i个数左边有多少N,右边有多少I
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
int n;
scanf("%d",&n);
string a;
cin>>a;
int nn=0,ii=0;
for(int i=0;i<n;i++){
N[i]=nn;
if(a[i]=='N')nn++;
}for(int i=n-1;i>=0;i--){
I[i]=ii;
if(a[i]=='I')ii++;
}//前文说的是如果为O才存储,这里为了省事,直接赋值。
ll ansn=0,anso=0,ansi=0;
int minn=0x7fffffff,j;
// ansn表示添加N,anso表示添加O,ansi表示添加I。
//三个ans因为可能会到1e5/2*1e5/2,可能会爆
//minn和j用来寻找添加O的位置
for(int i=0;i<n;i++){
if(a[i]=='O'){
ansn+=(N[i]+1)*I[i];
ansi+=N[i]*(I[i]+1);
//计算
if(abs(N[i]-I[i])<=minn){
minn=abs(N[i]-I[i]);
j=i;
//如果相差的更少就更新,找到相差最少的一个加O
}
}
}for(int i=0;i<n;i++){
if(a[i]=='O'){
if(i==j)anso+=N[i]*I[i];
anso+=N[i]*I[i];
//如果这个地方是相差最少的就*2
}
}cout<<max(max(ansn,ansi),anso);
//输出
return 0;
//fclose(stdin);
//fclose(stdout);
}
AC快乐!