洛谷 P1371 NOI元丹

题目描述

小A打算开始炼 NOI 元丹(什么鬼),据说吃了可以提高 NOI 时的成绩。

是这么练的。元丹有三种元核,NOI。现有很多个这样原核,按顺序排成一行。炼元丹时,从左往右分别挑出 NOI 三个原核吞下。

现在他关心,有几种服用方式……且慢!

他觉得服用方式太少,以至于不能成仙。所以他可以通过某个途径,得到 NOI 的三种原核中的任意一个,至于哪一种由他决定。然后他将获得这个原核的插入到这一排原核中的任意位置(包括最前最后)。

现在你要知道,新的元核序列中能有多少种 NOI 的取出方式。子串的字母并不要求连续。

输入格式

第一行,一个整数 N,表示字符串的长度。

第二行,一行字符串,里面只有只有 NOI 三种字母。

输出格式

表示出最多可以提炼出来的 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快乐!

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值