知识又双叒叕陷入了瓶颈期,所以今天咱们来玩点新鲜的。
1.字符串col编码定义
在牵扯到char类型的算法当中,半数牵扯到ASCII。我们都知道,ASCII字符集中的字符用数字编码表示(当然在C++实现中使用其主机系统的编码)。例如,字母A的编码为65,字母Z的编码是90。那么Rick的编码呢?
很显然,这个Rick指代不明。如果带上双引号写成"Rick"这样,那是没有编码的。因为此时"Rick"成为一个string类(或其他)的字符串,常理来说,字符串不具有编码属性。但这个Rick若成为一个变量名,这还有的商量。
那么如何让字符串显式地带有编码属性呢?咱们可以人为定义一个,就叫他集体编码(Colcode)好了。比如,若使一个string类字符串的编码值,等于字符串当中每个字符的ASCII值之和。即
s∈string且strlen(s)==n;则定义Colcode(s)=Σ(i=0,n-1)ASCII(s[i])
意思就是:咱们定义一个字符串的Colcode(即col编码值),等于字符串中每个字符的ASCII编码之和。
2.(例1)col码汇总
所以请问,一个字符串如何知道他的col码呢?我们用代码可以实现。
普通版
#include<bits/stdc++.h>
using namespace std;
int main()
{
unsigned long long cnt=0;
string s;
getline(cin,s);
for(int i=0;i<s.size();i++)
cnt+=int(s[i]);
cout<<cnt<<endl;
}
运行可以得到:
但这个程序的缺陷就是,如果遇到超长的字符串(远比你想象的长),那unsigned long long都无法容下(属于极端情况)。下面是
高精度版
#include<bits/stdc++.h>
using namespace std;
struct bigint
{
int len,a[1000];
bigint(int x=0)
{
memset(a,0,sizeof a);
for(len=1;x;len++)
a[len]=x%10,x/=10;
len--;
}
int &operator[](int i)
{
return a[i];
}
void flatten(int lt)
{
len=lt;
for(int i=1;i<=len;i++)
a[i+1]+=a[i]/10,a[i]%=10;
while(!a[len])
len--;
}
void print()
{
for(int i=max(len,1);i>=1;i--)
cout<<a[i];
}
};
bigint operator+(bigint a,bigint b)
{
bigint c;
int len=max(a.len,b.len);
for(int i=1;i<=len;i++)
c[i]+=a[i]+b[i];
c.flatten(len+1);
return c;
}
int main()
{
bigint cnt(0);
string s;
getline(cin,s);
for(int i=0;i<s.size();i++)
cnt=cnt+int(s[i]);
cnt.print();
}
3.(例2)col码列举
那么请问,如果已知一个大写字母字符串的col编码,那字符串有哪些可能呢?先来看一个
简化版的问题
已知一个字符串由两个大写字母组成,其col编码值为155,那这个字符串可能是什么呢?
或许可以用两层循环实现。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
for(int i=65;i<=90;i++)
for(int j=65;j<=90;j++)
if(i+j==n) cout<<char(i)<<" "<<char(j)<<endl;
}
那如果原题是三个大写字母呢?(三层循环)
如果一百个大写字母呢?(……一百层循环)
如果两兆个呢?(……慢慢敲)
这还不是硬伤。
硬伤是,如果不给出这个字符串长度,要求直接列出所有的可能呢?
有的人用分支语句。毕竟:只要头够铁,多少循环都能干出来、敲出来。
但有的人想要一种更省键盘的方法。
咱们需要用到搜索(一种算法)。具体可以去看《洛谷》的第14章或oi-wiki网站。
那么本题需要用到DFS(Depth-First-Search,深度优先搜索)算法。
DFS算法讲解:一道例题
int m, arr[103]; // arr 用于记录方案
void dfs(int n, int i, int a)
{
if (n == 0)
{
for (int j = 1; j <= i - 1; ++j) printf("%d ", arr[j]);
printf("\n");
}
if (i <= m)
{
for (int j = a; j <= n; ++j)
{
arr[i] = j;
dfs(n - j, i + 1, j); // 请仔细思考该行含义。
}
}
}
// 主函数
scanf("%d%d", &n, &m);
dfs(n, 1, 1);
imp:该范例的思路是,已知一个总和,先在第一个空填数,然后将总和减去这个数,然后再在第二个空填数,然后继续递减。当总和只剩0时,说明列举完成,进行整理。这很明显是操作相似但规模缩小的算法。
那么,不知道大家是否发现,这个搜索的逻辑细节和咱们手写这种题是不一样的。
比如用手写6=x+y+z(x, y, z∈N+ 且 x<y<z),我们肯定会令第一个数为1。
那么就成了6=1+y+z。
那么继续填y,又因为y≥x,就成了6=1+1+z。
那么这个时候,大家会填什么呢?
我会填z=4。
但是此程序中,仍然填的是z=1。
回看程序在main()函数中的首次调用。
dfs(n, 1, 1);
很明显,第一次调用中a=1,所以开始有j=1。
然后二次递归,新的n=5,新的a=1,继续进行迭代,一直到最后,每个数只要满足比前面大即可,所以三个数都填了1。
那么问题来了,6不可能等于1+1+1,那怎么办呢?
将第3个数填完以后,新的n=3,新的i=4。
新调用中,“n==0”和“i<=m”两个分支都不满足。所以此次调用只是走了一遍,他不进行任何操作。
那么回到上一层递归,由于递归返回,所以循环继续迭代,此时成了1+1+2。
当然还不符合要求,所以继续一个一个试。
所以很明显,此种搜索算法只是省了键盘,但是思路没有变化,他和循环方法的思路一样略为繁杂,这也符合这种算法的基本特点。
其次,该范例有问题。运行可以看出来。
原提要求裁成m个正整数,但运行后发现参差不齐。什么原因呢?
类比imp处的思路,有3个数字格,要求和为6。请注意这一行。
for (int j = a; j <= n; ++j) ;
第一次传进去的参数是1,那么a初始等于1,但是n=6,所以a迭代必然会迭代到6。
那么arr[i]为6,又因为i=1,所以arr[i]=6,故n-j=0,进行二次递归。
此时n-j成为了新的n,新的n=0,符合第一个分支(n==0),所以进行了输出。
那我们如何解决这个问题呢?
请大家回看代码,参数"i"是什么?
答:i是待填入数的指针。
那么i-1是?答:已填入数的数量。
这不就完了?
只需在n==0后面补加一个条件:i-1==m,意思是保证已填入数的数量(i-1)与要求的数量(m)相等。
这不就好了?
上完整代码
#include<iostream>
using namespace std;
int m, arr[103]; // arr 用于记录方案
int n;
void dfs(int n, int i, int a)//n为除掉前i-1个数外剩余数的总和,n为坐标迭代器,a为迭代的开始
{
if (n == 0 && i - 1 == m) //剩余数和已经等于0,且已经填满
{
for (int j = 1; j <= i - 1; ++j) printf("%d ", arr[j]);
printf("\n");
}
if (i <= m)
{
for (int j = a; j <= n; ++j)
{
arr[i] = j;
dfs(n - j, i + 1, j);
}
}
}
int main()
{
scanf("%d%d", &n, &m);//可以换成 cin>>n>>m;
dfs(n, 1, 1);
}
col码列举原题
大家也可以去看洛谷U408212(出错了 - 洛谷)题。
那么对于“已知一个大写字母字符串的col编码,字符串有哪些可能”这样的问题,如何解决呢?
1.上面这道题有数列单调不减的要求,但是这个没有;上一篇代码中的dfs()函数中,参数a就是用来满足这个要求的,所以完全可以删除这个参数。
2. for (int j = a; j <= n; ++j)
将这句改成从65(‘A’的ASCII值)到90(‘Z’的ASCII值)的迭代。
3.去除我刚才添加的i-1==m这个条件。并在后面加上这个。
for(int k=1;k*65<=s;k++)
if(k*90<s) continue;
else
{
m=k;
dfs(s, 1);
}
请大家思考一下这几句的含义。当然,这几句的作用,用其他的方法也可以代替(请大家自行尝试)。
类比这个思路,请问,如果已知一个小写字母字符串的col编码,那字符串有哪些可能呢?
大家可以去做洛谷U408427《又见Col编码》题。
欢迎讨论区交流。