2020智算之道-热身赛第二场-高校组第二场

水一篇题解,不多解释

热身赛

A题

2020智算之道-热身赛第二场-高校组第二场_第1张图片
可以看到,n只有1e5,m只有100,直接暴力就可以了,开一个1e5大小的桶标记所有数字,如果这个数字是ai的倍数,标记被淘汰,执行m次。其他方法,大概可以试试把m个数字素因子分解,然后用类似线性筛的方法预处理,但是估计跑的更慢···

#include 
#include 
using namespace std;
const int N=1e5+1;
int a[N]={0};
int main(){
	int n,m; scanf("%d%d",&n,&m);
	while(m--){
		int x; scanf("%d",&x);
		for(int i=x;i<=n;i+=x){
			a[i]=1;
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++)
		if(!a[i])
			cnt++;
	printf("%d\n",cnt);


	return 0;
}

B题

2020智算之道-热身赛第二场-高校组第二场_第2张图片
倒着枚举就好了,因为每次操作都只对当前位置前面的开关起作用,相对的,最靠后的按钮就是必须按下去的按钮,依次类推,从后往前按下所有按钮即可。但是我们不能每次真的把0变成1,1变成0,复杂度太高了,所以我们用一个cnt做标记。如果cnt=0,就正常1是1,0是0;如果cnt=1,说明我们有操作,那就把1看成0,0看成1.整个过程再用异或运算优化一下,这个优化最好自己手推几个数据试一下,或者debug一下就不难理解。

#include 
#include 
using namespace std;
const int N=2e5+1;
char x[N];
int main(){
	int len; scanf("%d",&len);
	scanf("%s",x);
	int cnt=0,cur=0;
	for(int i=len-1;i>=0;i--){
		x[i]-='0';
		if(x[i]^cur){
			cnt++;
			cur^=1;
		}
	}
	printf("%d\n",cnt);


	return 0;
}

C题

2020智算之道-热身赛第二场-高校组第二场_第3张图片
其实原本不想写这题的代码·····
说下思路先,很明显我们要用给出的串打乱顺序凑出T的子串,反过来想,我们只要枚举T的子串,看看这个子串能不能被凑出来就可以了。这样既好写又方便去重。
写法1就是直接取出所有的子串,然后放入set去重,或者放入map然后自己手动判断一下是否这个子串和之前的子串重复。然后判断子串的字母数量,如果子串能被给出的串凑出来,那么字母数量应该是<=的关系。
然后是怎么取子串。首先,子串长度肯定和给出的串相同,记为len,考虑顺次取出每一个长度len的子串,相当于在前一个子串的基础上去掉第一个字母,然后在尾部补一个字母,这样就能O(1)完成操作,但是我这里直接string的substr操作,懒得写。
然后,兴致勃勃地交上代码,90分,MLE,好,换写法。
考虑我们要完成的是字符串的存取和判重,直接用hash即可,然后因为是所有长度为len的子串依次取出,考虑滚动hash,即按照我们前面提到的这个子串和上一个子串的关系,既然上一个子串的hash值已知,那么,我只需要去掉第一个字母的权重,左移一位,然后再补上一个新字母的权重即可,这就是滚动hash。上代码(本人hash垃圾,不会选base和mod,WA了好多发,最后hash跑两个才AC,提醒大家mod要大一点)

#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int N=2e5+1;
const int mod1=1e7+3;
map<pair<LL,LL >,bool>q;
const int base1=2;
const int base2=3;
//const int base3=5;
int num[27]={0},cur[27]={0};
LL vhash1=0,vhash2=0,vhash3=0;
bool q1[mod1+5],q2[mod1+5];
LL quickpow(LL x,LL k,LL mod){
	LL res=1;
	while(k){
		if(k&1){
			res=res*x%mod;
		}
		x*=x; x%=mod;
		k>>=1;
	}
	return res;
}
int main(){

	string x,y; cin>>x>>y;
	int len1=x.size(),len2=y.size();

	for(int i=0;i<len1;i++){
		num[x[i]-'a']++;
		cur[y[i]-'a']++;
		vhash1*=base1;
		vhash1+=y[i];
		vhash1%=mod1;

		vhash2*=base2;
		vhash2+=y[i];
		vhash2%=mod1;

	}
	q1[vhash1]=1;
	q2[vhash2]=1;
	//cout<<"hash"<
	int res=0;
	for(int i=0;i<26;i++){
			if(cur[i]>num[i])
				break;
			if(i==25){
				res++;
				//cout<<0<
			}

	}
	int t1=quickpow(base1,len1-1,mod1);
	int t2=quickpow(base2,len1-1,mod1);
	for(int i=1;i<=len2-len1;i++){
		vhash1-=(y[i-1])*t1%mod1;
		vhash1*=base1;
		vhash1+=y[i+len1-1];
		vhash1=(vhash1%mod1+mod1)%mod1;
		//cout<<"hash"<
		vhash2-=(y[i-1])*t2%mod1;
		vhash2*=base2;
		vhash2+=y[i+len1-1];
		vhash2=(vhash2%mod1+mod1)%mod1;


		cur[y[i-1]-'a']--;
		cur[y[i+len1-1]-'a']++;
		if(q1[vhash1]&&q2[vhash2]) continue;
		q1[vhash1]=1;
		q2[vhash2]=1;

		for(int j=0;j<26;j++){
			if(cur[j]>num[j])
				break;
			if(j==25){
				res++;
				//cout<
			}
	}
	}
	cout<<res<<endl;

	return 0;
}

初赛

A

2020智算之道-热身赛第二场-高校组第二场_第4张图片
挺坑人的一个题目,思维题,当三个灯亮的时候肯定是位于中间的楼层,两个灯亮的时候要特判,因为如果一共只有两层,两个灯亮就不知道在第一层还是第二层。

#include 
#include 
#include 
using namespace std;

int main()
{
	int T; scanf("%d",&T);
	while(T--){
		int n,m; scanf("%d%d",&n,&m);
		int a[4];
		for(int i=0;i<m;i++){
			scanf("%d",a+i);
		}

		if(n==1){
			printf("1\n");
			continue;
		}
		else if(n==2){
				printf("-1\n");
				continue;
		}

		if(m==3)
			printf("%d\n",a[1]);
		else if(m==2){
			if(a[1]==2)
				printf("1\n");
			else
				printf("%d\n",n);
		}

	}
    return 0;
}

B

2020智算之道-热身赛第二场-高校组第二场_第5张图片
这题贪心就好,尽可能的把一样的字符放在前面,只要数量够就放,那么数量就=字符数量/要凑成的字符串数量,(向下取整,因为可能不是整除),然后26个字母都做一遍,求和。可能爆int.

#include 
#include 
#include 
using namespace std;

int main()
{
    int n; scanf("%d",&n);
    long long s=0;
    for(int i=0;i<26;i++){
		int x; scanf("%d",&x);
		s+=x/n;
    }
    printf("%lld\n",s);
    return 0;
}

C

2020智算之道-热身赛第二场-高校组第二场_第6张图片
这题,还是早早联想画图吧,简单想法就是如果两个数,知道了其中一个,又知道了它们的和,就可以求出两个数字。这也是出题人的意思,关键在于,如果求出了这两个数字x,y,如果在这之前,a也和x相连,但是a和x都为止,现在x已知,那么a也能求出来。
也就是要能连带的求出数字。
也就是,x求出来之后,能把跟x有关系的数字都找到并求出来。这不就是个连通块么,并查集维护即可。然而我··没写并查集,准确的说,我准备先交个爆搜练练手,结果··它·· 过了··,这数据是真的··有点水了··也可能我剪枝之后不太好卡···
最重要的一点是,就是我爆搜,也是做不出来环的情况的··,数据里压根没有环··。
什么叫做环呢,如果你已知a,b的和,b c的和,c a的和,那么好了,三元一次不等式方程组,可解,也就是所有数字都不可知,只知道和,但是只要构成环就能求出来,这是我爆搜没做的地方。
爆搜版本:

#include 
#include 
#include 
#include 
using namespace std;
const int N=3e5+10;
int a[N];
vector<int>g[N];
int cnt=0;
void dfs(int x){
	a[x]=1;
	cnt++;
	for(int i=0;i<(int)g[x].size();i++){
		int to=g[x][i];
		if(a[to]==0){
			dfs(to);
		}
	}
	g[x].clear();
}
int main()
{
    int n,m; scanf("%d%d",&n,&m);
    while(m--){
		int opt,x,y; scanf("%d",&opt);
		if(opt==1){
			scanf("%d",&x);
			if(a[x]==0)
				dfs(x);
		}
		else{
			scanf("%d%d",&x,&y);
			if(a[x]^a[y]){
				if(a[x]==0)
					dfs(x);
				else
					dfs(y);
			}
			else if(a[x]==1&&a[y]==1){
				;
			}
			else{
				g[x].push_back(y);
				g[y].push_back(x);
			}
		}
		printf("%d\n",cnt);

    }
    return 0;
}

赛后队友给的并查集版本(找环就是看看这两个点是不是原本就在一个连通块即可,但是重边还是不能做,重边还得单独搞)

#include 
#include 
#include 
using namespace std;
const int maxn=3e5+5;
int n,m;
bool flag[maxn];
int num[maxn];
int fa[maxn];
int opt;
int x,y;
int cnt;
int find(int a)
{
    while(a!=fa[a])
    a=fa[a];
    return a;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        num[i]=1;
        fa[i]=i;
    }
    for(int i=1;i<=m;++i)
    {
        scanf("%d",&opt);
        if(cnt==n)
        printf("%d\n",n);
        else
        if(opt==1)
        {
            scanf("%d",&x);
            int tmp=find(x);
            if(!flag[tmp])
            {
                flag[tmp]=1;
                cnt+=num[tmp];
            }
        }
        else
        {
           scanf("%d %d",&x,&y);
           if(x==y)
           {
               int tmp=find(x);
               if(!flag[tmp])
               {
                    flag[tmp]=1;
                    cnt+=num[tmp];
               }
            }
            else
            {
                int f1=find(x);
                int f2=find(y);
                if(flag[f1]&&!flag[f2])
                {
                    flag[f2]=1;
                    fa[f1]=f2;
                    cnt+=num[f2];
                    num[f2]+=num[f1];
                }
                else
                if(!flag[f1]&&flag[f2])
                {
                    flag[f1]=1;
                    fa[f1]=f2;
                    cnt+=num[f1];
                    num[f2]+=num[f1];
                }
                else
                if(!flag[f1]&&!flag[f2])
                {
                    if(f1!=f2)
                    {
                        fa[f1]=f2;
                        num[f2]+=num[f1];
                    }
                }
            }
        }
        printf("%d\n",cnt);
    }
    return 0;
}

你可能感兴趣的:(2020智算之道-热身赛第二场-高校组第二场)