引言
今天是过完年的新的一天,做了三道题,接触到一个新的概念叫贡献法,也是一种不错的解题思路,只要一直坚持下去,就一定能取得胜利,加油!
一、超级胶水
标签:数学
思路:
这道题其实很有意思,因为合并的方案数不论是怎样的,结果都是一样的。比如说有三个石子
a
,
b
,
c
a,b,c
a,b,c ,如果先合并前两个石子,得到的结果是
a
b
+
b
c
+
a
c
ab+bc+ac
ab+bc+ac ,如果是先合并后两个石子,那么结果也是
a
b
+
b
c
+
a
c
ab+bc+ac
ab+bc+ac ,所以大胆猜一下结果有可能就是任意方案都是一样的结果,事实也是如此,可以用数学归纳法推一下,这里就不推了。
题目描述:
小明有 n 颗石子,按顺序摆成一排。
他准备用胶水将这些石子粘在一起。
每颗石子有自己的重量,如果将两颗石子粘在一起,将合并成一颗新的石子,重量是这两颗石子的重量之和。
为了保证石子粘贴牢固,粘贴两颗石子所需要的胶水与两颗石子的重量乘积成正比,本题不考虑物理单位,认为所需要的胶水在数值上等于两颗石子重量的乘积。
每次合并,小明只能合并位置相邻的两颗石子,并将合并出的新石子放在原来的位置。
现在,小明想用最少的胶水将所有石子粘在一起,请帮助小明计算最少需要多少胶水。
输入格式
输入的第一行包含一个整数 n,表示初始时的石子数量。
第二行包含 n 个整数 w1,w2,…,wn,依次表示每颗石子的重量。
输出格式
一个整数表示答案。
数据范围
1≤n≤105,1≤wi≤1000
输入样例1:
3
3 4 5
输出样例1:
47
输入样例2:
8
1 5 2 6 3 7 4 8
输出样例2:
546
示例代码:
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
int n;
int main()
{
scanf("%d", &n);
LL res = 0, sum = 0;
while(n--)
{
int t;
scanf("%d", &t);
res += sum * t;
sum += t;
}
cout << res << endl;
return 0;
}
二、字串分值
标签:枚举、贡献法
思路:
一般思路就是枚举每个字串,然后判断,这样明显会超时,可以换一种思路。每个字符可以划分为多少个字串,可以让该字符有所贡献,如果该字符当前位置为
k
k
k 的话,左边第一次出现该字符的位置为
l
l
l ,右边第一次出现该字符的位置为
r
r
r ,那么该字符的贡献度就为
(
k
−
l
)
∗
(
r
−
k
)
(k-l)*(r-k)
(k−l)∗(r−k) ,然后遍历每个字符相加起来就行了
题目描述:
对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中恰好出现一次的字符个数。
例如 f(“aba”)=1,f(“abc”)=3, f(“aaa”)=0。
现在给定一个字符串 S[0…n−1](长度为 n),请你计算对于所有 S 的非空子串 S[i…j](0≤i≤j<n), f(S[i…j]) 的和是多少。
输入格式
输入一行包含一个由小写字母组成的字符串 S。
输出格式
输出一个整数表示答案。
数据范围
对于 20% 的评测用例,1≤n≤10;对于 40% 的评测用例,1≤n≤100;
对于 50% 的评测用例,1≤n≤1000;
对于 60% 的评测用例,1≤n≤10000;
对于所有评测用例,1≤n≤100000。
输入样例:
ababc
输出样例:
21
样例说明所有子串 f 值如下:
a 1
ab 2
aba 1
abab 0
ababc 1
b 1
ba 2
bab 1
babc 2
a 1
ab 2
abc 3
b 1
bc 2
c 1
示例代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int n;
char str[N];
int l[N], r[N], p[26];
int main()
{
scanf("%s", str+1);
n = strlen(str+1);
for(int i = 1; i <= n; ++i)
{
int t = str[i] - 'a';
l[i] = p[t];
p[t] = i;
}
for(int i = 0; i < 26; ++i) p[i] = n+1;
for(int i = n; i; --i)
{
int t = str[i] - 'a';
r[i] = p[t];
p[t] = i;
}
LL res = 0;
for(int i = 1; i <= n; ++i)
{
res += (LL)(i - l[i]) * (r[i] - i);
}
cout << res << endl;
return 0;
}
三、消除游戏
标签:模拟、链表
思路:
这道题整体就是先枚举一遍,把所有字符加入到备选集合,然后无限循环,把符合条件的字符加入到待删集合,然后删除的过程继续把相应的字符加入到待选集合,就这样不循环,等到待删集合中是空的,那就说明当前字符已经不变了,那就停下来了,每个字符按初始的下标定义,用双链表串起来。
题目描述:
在一个字符串 S 中,如果 Si=Si−1 且 Si≠Si+1,则称 Si 和 Si+1 为边缘字符。
如果 Si≠Si−1 且 Si=Si+1,则 Si−1 和 Si 也称为边缘字符。
其它的字符都不是边缘字符。
对于一个给定的串 S,一次操作可以一次性删除该串中的所有边缘字符(操作后可能产生新的边缘字符)。
请问经过 2^64 次操作后,字符串 S 变成了怎样的字符串,如果结果为空则输出 EMPTY。
输入格式
输入一行包含一个字符串 S。
输出格式
输出一行包含一个字符串表示答案,如果结果为空则输出 EMPTY。
数据范围
对于 25% 的评测用例,|S|≤103,其中 |S| 表示 S 的长度;对于 50% 的评测用例,|S|≤104;
对于 75% 的评测用例,|S|≤105;
对于所有评测用例,|S|≤106,S 中仅含小写字母。
输入样例1:
edda
输出样例1:
EMPTY
输入样例2:
sdfhhhhcvhhxcxnnnnshh
输出样例2:
s
示例代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6+10;
int n;
char str[N];
int l[N], r[N];
vector<int> q, w; //q为备选集合,w为待删集合
bool st[N];
void insert(int k)
{
if(!st[k])
{
st[k] = true;
w.push_back(k);
}
}
void filter_del()
{
w.clear();
for(int k: q)
{
int a = l[k], b = k, c = r[k];
if(str[a] == str[b] && str[b] != str[c] && c != n+1)
{
insert(b);
insert(c);
}
else if(str[b] == str[c] && str[b] != str[a] && a)
{
insert(b);
insert(a);
}
}
}
int main()
{
scanf("%s", str+1);
n = strlen(str+1);
str[0] = str[n+1] = '#';
for(int i = 1; i <= n; ++i)
{
l[i] = i-1, r[i] = i+1;
q.push_back(i);
}
r[0] = 1, l[n+1] = n;
while(true)
{
filter_del();
if(w.empty()) break;
q.clear();
for(int k: w)
{
int a = l[k], b = k, c = r[k];
if(!st[a] && a && (q.empty() || a != q.back())) q.push_back(a);
if(!st[c] && c != n+1) q.push_back(c);
r[a] = c, l[c] = a;
}
}
if(r[0] == n+1) puts("EMPTY");
else
{
for(int i = r[0]; i != n+1; i = r[i]) printf("%c", str[i]);
}
return 0;
}