牛客寒假算法基础算法训练营2

再次感谢牛客网,让我知道自己到底有多菜,只想吐槽,没有别的想法了,只做出两道题,剩下的勉强看懂考点,已经放弃,先挂两道等题解。

1、处女座与重修费

题目描述
期末考试结束了,处女座发现很多人挂了大物,只能等着第二年重修,还要交400元的重修费。处女座突然想起有个学长和他讲过,如果学校哪一年缺钱了,那一年的大物试卷就会特别难。现在处女座有了所有人的成绩,处女座想知道如果所有挂科的人都在第二年重修,学校能赚多少重修费?

挂科是指一门课的分数小于60分。

输入描述:
第一行一个整数n,表示考试的人数。
第二行n个整数,表示每个人的成绩。
1<=n<=10000
学生的成绩为0-100(包括0和100)之间的整数

输出描述:
一行,学校能赚的重修费用

示例1

输入
4
60
56
100
59

输出
800

这才是真正的签到题,没什么难度,给你点小自信假装很简单

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,cnt=0;
	cin>>n;
	while(n--)
	{
		int temp;
		cin>>temp;
		if(temp<60)cnt++;
	}
	cout<<cnt*400<<endl;
	return 0;
}

2、处女座的期末复习

题目描述
快要期末考试了,处女座现在有n门课程需要考试,每一门课程需要花ai小时进行复习,考试的起始时间为bi,处女座为了考试可以不吃饭不睡觉,处女座想知道他能否复习完所有的科目(即在每一门考试之前复习完该科目)。每一门课的考试时间都为两小时。

输入描述:
第一行一个整数n
第二行n个整数a1,a2,…,an,表示每门课需要复习的时间
第三行n个整数b1,b2,…,bn,表示每门课考试的时间
1<=n<=105
0<=ai<=109
0<=bi<=109

输出描述:
如果处女座能复习完,输出”YES”,否则输出”NO”

示例1

输入
3
0 1 1
2 6 4

输出
YES

说明
在0-1小时复习第2门课,
在1-2小时复习第3门课,
在2-4小时考第1门课,
在4-6小时考第3门课,
在6-8小时考第2门课
备注:
考试时不能复习,保证考试时间不会重叠。
复习可以拆开,只要复习时间够了即可。

一个比较基础的贪心算法题,先根据输入的数据sort一下,当考试时间相同时按照复习需要时间,不相同时按照考试时间排序,之后从最早考试的那一科开始,如果满足已经用的时间<=考试时间就说明可以满足条件,已经用的时间为考试时间加上复习时间。

AC代码如下

#include<bits/stdc++.h>
using namespace std;
struct Node {
	int time;
	int rank;
};
struct Node N[100005];
long long int t;
bool cmp(struct Node a,struct Node b)
{
	if(a.rank!=b.rank)
		return a.rank<b.rank;
	else
		return a.time<b.time;
}
int main()
{
	int n;
	cin>>n;
	t=0;
	for(int i=0;i<n;i++)
		cin>>N[i].time;
	for(int i=0;i<n;i++)
		cin>>N[i].rank;
	sort(N,N+n,cmp);
	int flag=1;
	for(int i=0;i<n;i++)
	{
		if(t+N[i].time+2*i<=N[i].rank)
			t+=N[i].time;
		else
		{
			flag=0;
			break;
		}
	}
	if(flag)
		cout<<"YES"<<endl;
	else
		cout<<"NO"<<endl;
	return 0;
}

另外还看了几道题,勉强知道考点,实在是打击人,主办方重新定义“基础”这个词语的意思,坐等题解吧。

3、处女座与宝藏
题目描述
处女座进行了一次探险,发现了一批宝藏。如果他获得这批宝藏,那么他一辈子都不需要工作了。但是处女座遇到了一个难题。
宝藏被装在n个宝箱里,宝箱编号为1,2,…,n,只有所有宝箱在某一时间被打开,处女座才能获得宝藏。有m个开关,每个开关控制k个宝箱,如果按下一个开关,那么这k个宝箱的开关状态都会发生改变(从开启变成关闭,从关闭变成开启),处女座想知道他能否获得这批宝藏。

输入描述:
第一行两个正整数n,m,
第二行n个整数,每个整数为0或1,表示初始时宝箱的状态,0表示开启,1表示关闭
接下来m行,每行开头一个整数k表示这个开关控制的宝箱的个数,接下来k个整数,表示控制宝箱的编号
1<=n,m<=200000
1<=k<=n
题目保证每个宝箱最多被两个开关控制。

输出描述:
一行,如果处女座能获得宝藏,输出”YES”,否则输出”NO”示例1

输入
4 4
1 0 1 1
2 3 4
2 1 3
1 2
2 1 2

输出
YES

一道2-SAT题目,头一次听说,研究了题解的代码研究了几个小时,勉勉强强看懂,用到了找有向图最大连通分量的方法,难度对菜鸡的我实在是太大了,具体都写在注释里了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
 
const int MAXN = 400010;
const int MAXM = 800010;
struct Edge
{
    int to,next;
}edge[MAXM];
int head[MAXN],tot;//tot用于记录当前存入的边数 ,head用于记录最后一个输入的以这个点为起点的边 

void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}

void addedge(int u,int v)
{
    edge[tot].to = v; 
	edge[tot].next = head[u]; 
	head[u] = tot++;
}

int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];
int Index,top;
int scc;
bool Instack[MAXN];
int num[MAXN];
 
 /*
 DFN[ i ] : 在DFS中该节点被搜索的次序(时间戳)
LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号
当DFN[ i ]==LOW[ i ]时,为i或i的子树可以构成一个强连通分量
 */ 
void Tarjan(int u)//用于找最大强连通分量 
{
    int v;
    
    Low[u] = DFN[u] = ++Index;
    
    Stack[top++] = u;//用数组实现了一个堆栈 
    Instack[u] = true;//记录是否进栈 
    
    for(int i = head[u];i != -1;i = edge[i].next)
    {
    	
        v = edge[i].to;//边的终点 
        
        if( !DFN[v] )//相当于进行了一次深搜 
        {
            Tarjan(v);
            if(Low[u] > Low[v])
				Low[u] = Low[v];
        }
        
        else if(Instack[v] && Low[u] > DFN[v])
        	Low[u] = DFN[v];
    }
    
    if(Low[u] == DFN[u])
    {
        scc++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;//全都出栈 
            Belong[v] = scc;//当前在堆栈中的节点全都在一个强连通分量中 
            num[scc]++;//记录当前强连通分量里面有多少个点 
        }
        while(v != u);
    }
}
 
bool solvable(int n)//n表示宝箱个数的两倍 
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    memset(num,0,sizeof(num));
    Index = scc = top = 0;
    for(int i = 0;i < n;i++)
    	if(!DFN[i])
    		Tarjan(i);//找出所有的强连通分量
    		
    for(int i = 0;i < n;i += 2)
        if(Belong[i] == Belong[i^1])
        	return false;
    return true;//只有在所有宝箱在一个连通分量中才可以满足条件 
}
 
int n,m;
int a[200005];
vector<int> v[200005];
 
int main()
{
    scanf("%d%d",&n,&m);
    init();
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);//记录初始状态 
    for (int i=0;i<m;i++)
    {
        int k;
        scanf("%d",&k);
        for (int j=1;j<=k;j++)
        {
            int x;
            scanf("%d",&x);
            v[x].push_back(i);//把每个每个宝箱被哪个开关控制存入向量 
        }
    }
    for (int i=1;i<=n;i++)
    {
        if (v[i].size()==2)//一个宝箱被两个开关控制时 
        {
            if (a[i]==1)//宝箱初始状态为关闭时 
            {
                int x=v[i][0]<<1;//移位运算,相当于乘二,移位运算运算速度比乘法快 
                int y=v[i][1]<<1;  
                //暂时性猜测,这里对每个数乘二是为了防止重复,把所有奇数变成偶数,这样所有偶数异或就不会重复
				//不然可能会出现重复,比如2^1=3.而3本身可能就是一个点,这样会出现错误
				 
                //1异或任何数-任何数表示取反
                addedge(x,y^1);
                addedge(x^1,y);
                addedge(y,x^1);
                addedge(y^1,x);
				//增加了四条边,由于异或相当于取反,为了保证最后状态是开,所以这两个开关只能选取一个开启
				//就是说,选X不选Y-选Y不选X-不选X选Y-不选Y选X,两种情况但是需要四条边 
            } 
            else//初始状态为开启时 
            {
                int x=v[i][0]<<1;
                int y=v[i][1]<<1;
                addedge(x,y);
                addedge(x^1,y^1);
                addedge(y,x);
                addedge(y^1,x^1);
                //原理同上 
            }
        }
        else if (v[i].size()==1)//被一个控制时 
        {
            if (a[i]==1)
            {
                int x=v[i][0]<<1;
                addedge(x,x^1);
                //这种情况下,表示必须选这个开关,用连一条从X到X逆的边表示必选 
            }
            else
            {
                int x=v[i][0]<<1;
                addedge(x^1,x);
                //这种情况下,表示必须不选这个开关,用连一条从X逆到X的边表示必不选 

            }
        }
        else if (v[i].size()==0)//一个也不被控制时 
        {
            if (a[i]==1)
            {
                addedge(0,0);
                addedge(1,1);
            }
        }
    }
    if (solvable(m<<1)) puts("YES");
    else puts("NO");
    return 0;
}

4、处女座与复读机
题目描述
一天,处女座在牛客算法群里发了一句“我好强啊”,引起无数的复读,可是处女座发现复读之后变成了“处女座好强啊”。处女座经过调查发现群里的复读机都是失真的复读机,会固定的产生两个错误。一个错误可以是下面的形式之一:
1.将任意一个小写字母替换成另外一个小写字母
2.在任意位置添加一个小写字母
3.删除任意一个字母
处女座现在在群里发了一句话,他收到了一个回应,他想知道这是不是一个复读机。

输入描述:
两行
第一行是处女座说的话s
第二行是收到的回应t

s和t只由小写字母构成且长度小于100
输出描述:
如果这可能是一个复读机输出”YES”,否则输出”NO”

示例1

输入
abc
abcde

输出
YES

说明
abc->abcd->abcde

示例2

输入
abcde
abcde

输出
YES

说明
abcde->abcdd->abcde

备注:
只要能经过两步变换就从s得到t就有可能是复读机。

答案给出的是模拟的方法,很容易想明白,但是题目存在很多值得优化的地方,网上有大神用动态规划做的也AC,此外针对答案给出的代码,在循环结束的位置上还有可以优化的地方,下面代码是做了部分改动的AC代码,循环结束的条件加上&&can可以减少循环次数,此外还在变化一位的情况做了修改,应该是cnt==1时才符合条件,而不是cnt<=1,虽然效果是一样的。具体思路写在注释里了。

#include <bits/stdc++.h>
using namespace std;
string a,b;
 
int main()
{
    cin>>a>>b;
    if (fabs((int)a.length()-(int)b.length())>2)//缩小范围,如果复读机说的比原话长度还要差别2位以上说明肯定不是 
        puts("NO");
    else
    {
        if (b.length()==a.length()+2)//增加两位 
        {
            int can=0;
            for (int i=0;i<b.length()&&!can;i++)
            {
                for (int j=i+1;j<b.length()&&!can;j++)
                {
                    string s="";
                    for (int k=0;k<b.length()&&!can;k++)
                    {
                        if (k!=i && k!=j) 
							s.push_back(b[k]);//s实际上就是字符串B除去两个字母以后的所有可能情况,如果所有情况中有和a相等的说明是由a变过来的 
                    }
                    if (a==s) can=1;
                }
            }
            if (can==1) puts("YES");
            else puts("NO");
        }
        else if ((int)b.length()==(int)a.length()-2)//减少两位 
        {
            swap(a,b);
            int can=0;
            for (int i=0;i<b.length()&&!can;i++)
            {
                for (int j=i+1;j<b.length()&&!can;j++)
                {
                    string s="";
                    for (int k=0;k<b.length()&&!can;k++)
                        if (k!=i && k!=j) s.push_back(b[k]);//和上面那种情况正好反过来,把B当做原字符串,如果去掉两个字母能得到A那就说明A可以通过增加两个得到B 
                    if (a==s) can=1;
                }
            }
            if (can==1) puts("YES");
            else puts("NO");
        }
        else if (b.length()==a.length()+1)//增加一位 
        {
        	//增加一位实际上就是增加了一个字母然后 剩下一个错误一定是写错了一个字母 
            int can=0;
            for (int i=0;i<b.length()&&!can;i++)
            {
                string s="";
                for (int j=0;j<b.length();j++)
                    if (j!=i) s.push_back(b[j]);//找出所有去掉一个字母的可能字符串 
                int cnt=0;
                for (int i=0;i<a.length();i++)
                    if (a[i]!=s[i]) cnt++;//在可能字符串里找,找写错字母的个数 
                if (cnt==1) can=1;//如果写错的字母个数不多于一个,说明符合复读机条件 
            }
            if (can==1) puts("YES");
            else puts("NO");
        }
        else if ((int)b.length()==(int)a.length()-1)//减少一位 
        {
            swap(a,b);//减少一位的情况与增加一位正好相反,调换一下A、B位置就好 
            int can=0;
            for (int i=0;i<b.length()&&!can;i++)
            {
                string s="";
                for (int j=0;j<b.length();j++)
                    if (j!=i) s.push_back(b[j]);
                int cnt=0;
                for (int i=0;i<a.length();i++)
                    if (a[i]!=s[i]) cnt++;
                if (cnt==1) can=1;
            }
            if (can==1) puts("YES");
            else puts("NO");
        }
        else//没有数位上的变化 
        {
            int can=0;
            for (int i=0;i<b.length()&&!can;i++)
            {
                for (int j=i+1;j<b.length()&&!can;j++)
                {
                    int cnt=0;
                    for (int k=0;k<b.length();k++)
                    {
                        if (k==i || k==j) continue;
                        if (a[k]!=b[k]) cnt++;
                    }
                    if (cnt==0) can=1;
                }
            }//这一重循环用于解决写错两个字母的情况,除了两个字母其他都一样,这时符合条件 
			for (int i=0;i<b.length()&&!can;i++)
            {
                for (int j=0;j<b.length()&&!can;j++)
                {
                    string s="";
                    string t="";
                    for (int k=0;k<a.length();k++)
                        if (k!=i) s.push_back(a[k]);
                    for (int k=0;k<b.length();k++)
                        if (k!=j) t.push_back(b[k]);
                    if (s==t) can=1;
                }
            }//没有数位变化的情况第二类可能是只写错了一个字母,但两次写错的位置都是一样的且第二写错的字母不是正确的字母
            if(a==b) can=1;//如果两次都是一个位置上的字母出错,且第二次正好错了回来,这种情况也是符合条件的
            if (can==1) puts("YES");
            else puts("NO");
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43849505/article/details/86628344