做了一周的算法题,在原有的算法基础上进行一些深化的题目。主要是将一些做过比较好的题的思路,并且一边做,一边也修改了一些之前发的文档优化解题方式。
类似于错题重错,一错再错,死不悔改,至死方休的记忆加深学习法。并且暴力的方式都pass掉,只讲算法解法
getline,获取此行的语句相比于cin,getline的优点就是在输入字符串的时候能够输入空格。而cin则会在输入空格时自动结束输入。
cin.getline(char* s, streamsize n, char delim),这里的参数char* s是输入的字符串变量, n是输入字符串的字符个数(第n个补’\0’), delim是输入终止条件,即遇到delim所代表的字符就终止输入。值得注意的是,在正常使用时 char delim可以省略,c++语言默认为’\0’。
getline(cin,str)
getline(istream& is, string& str, char delim)
is是标准输入流函数, str是用来存字符的变量名, delim是结束标志,此处作用与cin.getline()里的相同。
getline()是string流的函数,只能用于string类型的输入操作。
cin.getline是std流的函数,用于char*类型的输入操作。
*getchar可以吃掉空格,\n和\0都会被存储,一次吃一个,ch=getchar();
大小顺序: 数大小
48~57为阿拉伯数字0-9,65 ~ 90为26个大写英文字母A-Z,97 ~ 122为26个小写英文字母A-Z。
一些城市沿着一条笔直的公路建造。
这些城市从左到右编号为 1,2,3…
有 N 个 G 巴士在这条路上运行。
对于每个 G 巴士,我们知道它服务的城市范围:第 i 个 g 巴士为编号为 Ai 和 Bi 之间的城市(包含 Ai 和 Bi)提供服务。
现在给定你一个城市子集 P。
对于 P 中包含的每一座城市,我们需要找出有多少辆 G 巴士为该城市服务。
一开始看题目可以理解为从一条直线里的区间里,拉一堆线,找某点的公共线段数,较多抑或者少。根据一种输出入结果,进一步理解
在样例#1中,有四个 G 巴士。第一个服务城市 15 到 25,第二个服务城市 30 到 35,第三个服务城市 45 到 50,第四个服务城市 10 到 20。
城市 15 由第一个和第四个 G 巴士服务,城市 25 仅由第一个 G 巴士服务,因此答案输出为 2 1。
第一次看到使用桶排序的套层解法,也就是暴力,但这道题目的是让我们做出来差分。
对一段序列{0, 0, 0, 0, 0}, 如果我们想让它第二到第四个元素都加个1, 我们可以让第二个元素+1, 第五个元素-1,得到{0, 1, 0, 0, -1}, 再对它求一遍前缀和, 就可以得到{0, 1, 1, 1, 0}. 这就是差分的基本思想
本题几乎是裸差分问题, 对于每个巴士的服务范围[l, r], 我们在差分数组中令count[l] += 1, count[r+1]-=1即可, 最后将所有的巴士范围都插入到差分数组中后, 求一遍前缀和, 即为所有城市的巴士数量
#include
using namespace std;
const int N = 5e3+10;
int n,m,st[N];
int main()
{
int T;
cin>>T;
for(int cases=1;cases<=T;cases++){//几组数据
memset(st, 0, sizeof st);
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
st[l]++,st[r+1]--;
}//差分的预处理,某区间想变,也就是做基础变量,为之后的s差分机做预处理。
for(int i=1;i<N;i++)st[i]+=st[i-1];//差分机
cin>>m;
printf("Case #%d: ", cases);
while (m -- )
{
int x;cin>>x;
printf("%d ", st[x]);//基础的Q & A
}
puts("");
}
return 0;
}
艾伦和芭芭拉一起玩数字游戏。给定前 N 个正整数 1∼N。
首先,由艾伦从中选取一些数字(不能不取),然后,芭芭拉选取剩余所有数字(如果有的话)。
不妨设艾伦选取的所有数字之和为 A,芭芭拉选取的所有数字之和为 B。
现在,给定一个比率 X:Y,要求 A:B 恰好等于 X:Y。
请你给出一种艾伦选取数字的合理方案。
这种方法是数论的解决方法。需要设计的约数解法+贪心
S=sum(1~~N);A+B=S;
A/B=X/Y;gcd(X,Y)=1;A=CX,B=CY;
CX+CY=S;
存在解 S%(X+Y)==0;
把S分为(x / (x+y), y / (x+y) )两部分令A为任一个, 然后往结果数组中填数直到和为A
数学归纳法证明贪心算法的正确性
我们先搞个定义, 定义运用了上述贪心思想的函数 f(N, A), 作用是返回在1~N序列可能的 和为A的 子序列, 当函数f能正确返回我们需要的子序列时我们称函数f是正确的
数学归纳法三部曲
对任意正整数n, 对任意合理的A, f(n, A)都能返回1~n中sum为A的子序列, 因此证明了上述贪心思想是正确的
#include
using namespace std;
typedef long long LL;
const int N = 5010;
int n, x, y;
int ans[N];
int main(){
int T;
cin >> T;
for (int t = 1; t <= T; t ++)
{
cin >> n >> x >> y;
int sum = 0;
for (int i = 1; i <= n; i ++) sum += i;//总目的数
int b = sum / (x + y);//比率是否可以整除
if (sum % (x + y)) printf("Case #%d: IMPOSSIBLE\n", t);
else {
memset(ans, 0, sizeof ans);
printf("Case #%d: POSSIBLE\n", t);
int cnt = 0;
int s = x * b;
for (int i = n; i >= 1; i --)//贪心倒着拿最大的就行
{
if (s >= i)
{
s -= i;
ans[cnt ++] = i;
}
}
cout << cnt << endl;
for (int i = 0; i < cnt; i ++)
cout << ans[i] << ' ';
cout << endl;
}
}
}
给定一个非负整数数组 A,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。
返回 A 的正方形排列的数目。
两个排列 A1 和 A2 不同的充要条件是存在某个索引 i,使得 A1[i]≠A2[i]
dfs全排列,并且要考虑到重复方案也就是等效排列剪枝对于每一种数字,第一个相同数字用了才能用第二个相同数字。对于排序过的数组,如果当前元素与前一个元素相同并且前一个元素还没有用过,那么当前元素也不能使用。
#include
const int N=15;
int n,number[N];
int ans;
bool vst[N];
bool isSqr (int x)
{//判断是否为平方数
int SQRT=(int)sqrt(x);//取出平方根,要向下取整
return SQRT*SQRT==x;//如果平方根的平方与自己一样,那就是平方数
}
void dfs (int lst,int unvst)
{//lst表示目前队列中最后一个元素,unvst表示还未枚举的元素数量
if (unvst==0)
{//没有遍历过的元素全部没有了
ans++;//符合要求的答案再增
return;//返回
}
for (int iNum=1;iNum<=n;++iNum)
{//枚举数字
if (vst[iNum]) continue;//枚举过了,跳过
if (number[iNum]==number[iNum-1] and not vst[iNum-1]) continue;
//如果自己和上一个没被用过的数字一样,不能使用
if (lst==-1 or isSqr(lst+number[iNum]))
{//第一个元素或者满足题目要求
vst[iNum]=true;//加入队列
dfs(number[iNum],unvst-1);//继续深入
vst[iNum]=false;//不加入队列,撤销(,继续)
}
}
}
int main (){
cin>>n;
for (int i=1;i<=n;++i)
cin>>number[i];
sort(number+1,number+n+1);//排序
number[0]=-1;//防止一号判断出错
dfs(-1,n);//注意要初始化为-1,防止一号判断出错
cout<<ans<<endl;//输出答案
return 0;
}
链表题,最基本的套圈模型
N 个人围成一圈顺序编号,从 1 号开始按 1、2、3 顺序报数,报 3 者退出圈外,其余的人再从 1、2、3 开始报数,报 3 的人再退出圈外,依次类推。主要是适应一下链表的搭建,typedef和直接struct都可以,看清华的数据结构书上的比较套路化,建议使用那本书。
#include
using namespace std;
const int N = 51;
int T;
typedef struct TNode {
int data;
bool test;
struct TNode* next;
TNode (int x):data(x),test(true),next(NULL){}
}node,*linklist;
int main()
{
cin >> T;
while (T--) {
int n; cin >> n;
node *h, *t;
for (int i = 1; i <= n; i++) {
if (i == 1) { h = new node(1); t = h;}
else {
node* a = new node(i);
t->next = a;
t = t->next;
}
}
t->next = h;
t = h;
int cnt = 0;
while (n > 0) {
if (t->test == true) {
cnt++;
if (cnt % 3 == 0) {
cout << t->data << " "; n--; t->test = false;
}
}
t = t->next;
}
cout << endl;
}
return 0;
}
数论中从 n 个不同元素中取出 m(m<=n)个元素的所有排列的个数,叫做从 n 中取 m 的排列数,记为 p(n,m)。
具体计算方法为 p(n,m)=n(n−1)(n−2)……(n−m+1)=n!/(n−m)!(规定 0!=1)。
在此基础上引入进制表示
当 n 和 m 不是很小时,这个排列数是比较大的数值,比如 p(10,5)=30240。
如果用二进制表示为 p(10,5)=30240=(111011000100000)b,也就是说,最后面有 5 个零。
我们的问题就是,给定一个排列数,算出其二进制表示的后面有多少个连续的零。
数论分析后::二进制末尾有多少个0,就是p(n,m)中有几个2因子
Ans=fun(n,2)−fun(n−m,2)
#include
using namespace std;
const int N = 1e4+10;
int n,m,ans;
int get(int n,int p){
int s=0;
while(n)s+=n/=p;
return s;
}
int main(){
cin>>n>>m;
while(n||m){
ans=get(n,2)-get(n-m,2);
cout<<ans<<endl;
cin>>n>>m;
}
return 0;
}
给定一个 n 个结点(编号 1∼n)构成的二叉树,其根结点为 1 号点。进行 m 次询问,每次询问两个结点之间的最短路径长度。树中所有边长均为 1。
可以引入一个简单的逻辑,就是两个长度不同的链表找到公共点,会有la+lb与bl+al的循环差,那个点就是公共交集点,LCA也是如此,
当节点x比节点y深,让x向上爬树,变成自己的父节点;
当节点y比节点x深,让y向上爬树,变成自己的父节点。
此时两个节点深度相同,让两个节点同时爬树,直到重合为止,第一次重合的那一个节点就是它们的最近公共祖先。
int LCA (int x,int y)
{//寻找x和y的最近公共祖先
while(d[x]>d[y])
x=p[x];//x更深,让x往上走
while(d[y]>d[x])
y=p[y];//y更深,让y往上走
while(x!=y)//当两个节点不相等时
x=p[x],y=p[y];//两个节点深度相同时,同时往上爬树
return x;//返回x或y都可以
}
二叉树的优化点技能+1
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef long double ld;//较高精度的浮点数
typedef pair<int,int> PII;
typedef pair<string,int> PSI;
const int N=1009;
int n,m;
int l[N],r[N],p[N],d[N];
//l[i]表示左儿子、r[i]表示右儿子、p[i]表示父节点、d[i]表示深度
void dfs (int root,int depth)
{//root表示根节点,depth表示目前深度
if (root==-1)
return;//为-1,到底了,返回
d[root]=depth;//设置当前深度 重新建表确定他们的深度
dfs(l[root],depth+1);//左儿子深入
dfs(r[root],depth+1);//右儿子深入
}
int LCA (int x,int y)
{//寻找x和y的最近公共祖先
while(d[x]>d[y])
x=p[x];//x更深,让x往上走
while(d[y]>d[x])
y=p[y];//y更深,让y往上走
while(x!=y)//当两个节点不相等时
x=p[x],y=p[y];//两个节点深度相同时,同时往上爬树
return x;//返回x或y都可以
}
void input ()
{
cin>>n>>m;
for (int i=1;i<=n;++i)
{
int leftson,rightson;
cin>>leftson>>rightson;//输入左右儿子
l[i]=leftson;
r[i]=rightson;//设置当前节点的左右儿子
if (leftson!=-1)
p[leftson]=i;//如果左儿子不为空,它的父亲设为当前节点
if (rightson!=-1)
p[rightson]=i;//如果右儿子不为空,它的父亲设为当前节点
}
}
void query ()
{
for (int i=1;i<=m;++i)
{
int x,y;
cin>>x>>y;//输入两个询问的节点
cout<<d[x]+d[y]-2*d[LCA(x,y)]<<endl;
}
}
int main ()
{ int T;
cin>>T;//输入数据组数
for (int i=1;i<=T;++i)
{
input();//输入
dfs(1,1);//从根节点开始遍历,初始化深度为1或0都没有问题
query();//问询
}
return 0;
}
特殊数据DP
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。
在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。
每一项是一个从 1 到 365 的整数。
火车票有三种不同的销售方式:
一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。
例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。
返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。
重点写一下关于日期的操作,DP还是用之前的闫式DP
int get(int i, int cnt)
{
int day = i;
while (day >= 1 && days[day] >= days[i] - cnt + 1) day -- ;
return day;
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> days[i];
for (int i = 1; i <= 3; i ++ ) cin >> costs[i];
for (int i = 1; i <= n; i ++ )
{
f[i] = f[i - 1] + costs[1];
f[i] = min(f[i], f[get(i, 7)] + costs[2]);
f[i] = min(f[i], f[get(i, 30)] + costs[3]);
}
cout << f[n] << endl;
#include
using namespace std;
const int N=1e5+10;
int a[N],f[N];
int main(){
int n;
while(cin>>n){
for(int i=1;i<=n;i++){
cin>>a[i];
}
int sum=-0x3f3f3f3f,l,r;
for(int i=1,j=1;i<=n;i++){
f[i]=max(f[i-1],0)+a[i];
if(f[i-1]<0)j=i;
if(f[i]>sum)l=j-1,r=i-1,sum=f[i];
}
if(sum<0) puts("0 0 0");
else cout<<sum<<' '<<l<<' '<<r<<endl;
}
return 0;
}
给前中,求后遍历,题目很简单,主要是在纸上画一下分区间
void dfs (int l1,int r1,int l2,int r2)
{//代表当前状态为前序遍历的[l1,r1]范围,中序遍历的[l2,r2]范围。
if (l1>r1)//越界不合法
return;
int root=l2;
while(in[root]!=pre[l1])
++root;
//找到中序遍历的根
dfs(l1+1,l1+root-l2,l2,root-1);//左半部分对应的前序、中序遍历
dfs(r1-r2+root+1,r1,root+1,r2);//右半部分对应的前序、中序遍历
cout<<pre[l1];//输出根节点
}
又是一个DFS全排列+剪枝
把 M 个同样的苹果放在 N 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?
盘子相对顺序不同,例如 5,1,1 和 1,5,1 算作同一种分法。
于5 1 1 和 1 5 1 是同一种放法,所以我们最好是有顺序的枚举
这里我们从小到大枚举
dfs(i,x) i表示第i个盘子,x表示上一个盘子的苹果
当前苹果从x开始枚举,就可以保证枚举顺序是递增的,当前盘子的苹果数量一定大于等于上一个盘子的苹果数量
我们一共有m个苹果,所以在枚举完前n - 1个盘子后,最后一个盘子的苹果数量就固定了
根据递增的枚举顺序,如果最后一个盘子的苹果数量比上一个盘子的少
那这个方案在之前就一定已经枚举过了 (因为1 1 5 一定比 1 5 1先枚举)
#include
using namespace std;
int n,m;
int ans,all;
void dfs(int i , int x)
{
if (i == n)
{
if (m - all >= x) ans ++;
return ;
}
for (int j = x ; j <= m ; j ++)
{
all += j;
dfs(i + 1 , j);
all -= j;
}
}
int main()
{
while(cin >> m >> n)
{
ans = all = 0;
dfs(1,0);
cout << ans << endl;
}
return 0;
}
给定 N 个权值作为 N 个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。现在,给定 N 个叶子结点的信息,请你构造哈夫曼树,并输出该树的带权路径长度。永远逃不掉的哈夫曼树,在苹果合并模板题里也出现了,是图论题的钉子户。
可以自己手动写down up delete也可以直接greaterheap
#include
#include
using namespace std;
int main(){
int n;
cin>>n;
priority_queue<int,vector<int>,greater<int> > q;
for(int i=0;i<n;i++){
int x;cin>>x;
q.push(x);
}
int res=0;
while(q.size()>1){
int t1=q.top();q.pop();
int t2=q.top();q.pop();
q.push(t1+t2);
res+=t1+t2;
}
cout<<res<<endl;
return 0;
}
一共有 N 个首脑参加峰会,编号 1∼N。这些首脑之间存在 M 对两两之间的直接朋友关系。
在划分区域时,我们希望被安排在同一休息区域的首脑们满足,任意两人之间都是直接朋友关系。现在,给定 K 个关于划分休息区域的安排,请你依次判断每个安排是否合理。
细节条件如下
#include
using namespace std;
const int N=205;
int n,m,t;
int g[N][N];
int check(){
int l,a[N];
cin>>l;
unordered_set<int>s;
for(int i=1;i<=l;i++)cin>>a[i],s.insert(a[i]);
for(int i=1;i<l;i++)
for(int j=i+1;j<=l;j++)
if(!g[a[i]][a[j]])
return -1;
for(int i=1;i<=n;i++){
if(!s.count(i))
{
bool f=true;
for(auto k:s){
if(!g[i][k])f=false;
}
if(f)
return i;
}
}
return 0;
}
int main(){
cin>>n>>m;
while(m--){
int a,b;
cin>>a>>b;
g[a][b]=g[b][a]=1;
}
cin>>t;
for(int i=1;i<=t;i++){
int ans=check();
if(~ans){
if(ans)
printf("Area %d may invite more people, such as %d.\n",i,ans);
else
printf("Area %d is OK.\n",i);
}else
printf("Area %d needs help.\n",i);
}
return 0;
}
个人认为像是没有先后关系的搭档舞会,也是之前的那道模板题
一个算术数组是指至少包含两个整数,且相邻整数之间的差值都相等的整数数组。
例如,[9、10],[3、3、3] 和 [9、7、5、3] 是算术数组,而 [1、3、3、7],[2、1、2],和 [1、2、4] 不是算术数组。
Sarasvati 有一个包含 N 个非负整数的数组,其中的第 i 个整数为 Ai。请帮助她确定最长的连续算术子数组的长度。
本题需要计算最长的连续等差数列的长度
我们可以转化为差分数组 最长连续相等的长度 最后在加1即可
注意差分数组第一项 我们要设置为0 因为我们差分数组的含义在本题是该项与前一项的差
因为第一项没有前一项 我们就不能将其设置为a[1]
差分算法模型
#include
using namespace std;
const int N=2e5+5;
int T,n,a[N],d[N];
int main(){
cin>>T;
for(int t=1;t<=T;t++){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
d[i]=a[i]-a[i-1];//记录差分哦
}
d[1]=0;//第一项设置为0
int ans=0,l=1,r=1;
while(r<=n){//找相邻且相同的最长序列
if(d[r]!=d[l]){
ans=max(ans,r-l+1);
l=r;
}
r++;
}
ans=max(ans,r-l+1);
printf("Case #%d: %d\n",t,ans);
}
return 0;
}
正在尝试努力一下CCF认证,看了看之前的历年正题,同时做了当年的考试原题,当初一点也不会,现在回头做出来,发现简单多了,也证明了当时学习有多浮躁。CCF的讲解都烂大街了,直接留下来题目的链接,可以错题重做
只能说,这道题考研和刷题或者看书,连着遇到四次了,背过吧
#include
using namespace std;
const int N = 1e5+10;
stack<char> op;
stack<int> num;
void eval(){
auto b=num.top();num.pop();
auto a=num.top();num.pop();
auto c=op.top();op.pop();
int x;
if(c=='+')x=a+b;
else if(c=='-')x=a-b;
else if(c=='*')x=a*b;
else x=a/b;
num.push(x);
}
int main()
{
string s;cin>>s;
unordered_map<char,int> p{{'+',1},{'-',1},{'*',2},{'/',2}};
for(int i=0;i<s.size();i++){
if(isdigit(s[i])){
int j=i,x=0;
while(j<s.size()&&isdigit(s[j])){
x=x*10+s[j++]-'0';
}
num.push(x);
i=j-1;
}
else if (s[i]=='(')op.push(s[i]);
else if(s[i]==')'){
while(op.top()!='(')eval();
op.pop();
}
else{
while(op.size()&&op.top()!='('&&p[op.top()]>=p[s[i]])eval();
op.push(s[i]);
}
}
while(op.size())eval();
cout<<num.top()<<endl;
return 0;
}
输入一棵树的前序和中序得到重建的二叉树的方式,同样也是上面一道题的解,直接上函数公式
class Solution {
public:
unordered_map<int,int> pos;
TreeNode *dfs(vector<int> &pre,vector<int>&in, int a,int b,int x,int y){
if(a>b)return NULL;
auto root=new TreeNode(pre[a]);
int k=pos[root->val];
root->left=dfs(pre,in,a+1,k+a-x,x,k-1);
root->right=dfs(pre,in,b-y+k+1,b,k+1,y);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n=preorder.size();
for(int i=0;i<n;i++){
pos[inorder[i]]=i;
}
return dfs(preorder,inorder,0,n-1,0,n-1);
}
};
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。假设数组非空,并且一定存在满足条件的数字。重点看要求
假设要求只能使用O(n)O(n)的时间和额外O(1)O(1)的空间,该怎么做呢?
class Solution {
public:
int moreThanHalfNum_Solution(vector<int>& nums) {
int val, cnt = 0;
for (auto x : nums)
{
if (!cnt) val = x, cnt ++ ; //目标值与其他值刚好配对抵消时,重置计数
else
{
if (x == val) cnt ++ ;
else cnt -- ;
}
} return val; //最后剩下的一定是多于半数的目标值
}
};
这些题也只是一周左右的一些好玩的题目,也许并不难,但是有一些记忆点。主要是差分,DFS剪枝,模拟,二叉树的操作,其实难点是在CCF里,日常回归吧。
梦里有个女孩在镜湖边,她坠落倒下,涟漪吞没了她,而此时山峦相对。
美的破碎便是悲剧的内核。