启智树游记&题解——逆境中的奇迹

kradcigam

2020-06-26 16:17:06

Personal

[CSDN同步](https://blog.csdn.net/qq_46230164/article/details/106963322) 首先是 [广告](https://www.qizhishu.com/) (启智树官网) 诶,炸了就得自己承担,吸取经验下次不要再犯才是最重要的(自我安慰。。。) # $Day\ -?$ 得知了有这场比赛,名肯定是要报的,但是真的有点不情愿——这场比赛主要是小学生的,所以,对于我来说不拿第 $1$ ,第 $2$ 显然是说不过去。。。 但愿不会出意外吧…… # $Day\ -1$ 知道了规则,感觉挺意外的——还要开摄像头和麦克风。当然,这是好事,不让某某某这样的同学误入作弊的歧途 # $Day\ 0$ 作业多多多,睡前都在做作业,怕不好明天考试还会去做作业(~~直播写作业我怕不是要翻车,原形毕露了~~~ # $Day\ 1$ 可惜出题人没给我留下机会/kk ## $T_1$ ### 题目描述 >2020年春节,新型冠状病毒(后文简称新冠病毒)肆虐,居民必须佩戴口罩方能出门,一时之间口罩成为紧缺物资,各地政府为了避免出现哄抢现象,纷纷出台各种措施,限制购买口罩的数量。淘淘所在社区就规定必须持身份证到指定药房排队购买一次性口罩,并且每户每天只能派出一人购买,每次限购k(k&gt;1)只。每次出门采购一次性口罩,将消耗家里原有一次性口罩1只。由于药房每天能够供应的一次性口罩是有限的,并不能保证排队的人都能买到,也就是说买到了就赚到k-1只,买不到就亏1只。已知淘淘家里原有一次性口罩n只,出门若干次之后,家里的一次性口罩变成了t只,请问淘淘至少出门了几次? ### 做法&想法 做得挺顺利,应该不大会翻车。 我们显然有个贪心,显然有一种策略——我们绝对不会先去赚 $k-1$ 只,在去亏 $k-1$ 只,因为题目求的是最少出门次数。 ### 代码 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } int main(){ int n,t,k,ans=0;read(n);read(k);read(t);k--; if(n>=t)return printf("%d\n",n-t),0; int x=t-n; if(x%k==0)ans+=x/k; else ans+=x/k+1; n+=ans*k; ans+=n-t; printf("%d\n",ans); return 0; } ``` ## $T_2$ ### 题目描述 >新冠病毒把每个人的生活节奏都打乱了,搞得大家都不能出门,更别提上幼儿园了,可把淘淘的小妹妹蓝蓝憋坏了,小家伙在家老是缠着妈妈讲故事,每当此时妈妈会讲一些“从前有座山,山里有座庙,庙里有个老和尚给小和尚讲故事,讲的什么呢?从前有座山……”这样循环的故事,一直讲到蓝蓝睡着或不想听为止。元宵节过后妈妈要上班了,为了满足蓝蓝听妈妈讲故事的需求,妈妈要淘淘把她讲的故事录音后用复读机循环播放给蓝蓝听,淘淘觉得整个故事有点长,全录下来没有必要,如果一个故事本身是周期性的,那么只要找出它的最小周期,将开头一段长度等于最小周期的内容录下来用复读机循环播放就行了。如故事内容为“abcabcabcabc”,它的最小周期长度为3,内容为“abc”,只需将“abc”录下来循环播放就行了。淘淘很忙,他把这个任务交给了你,要求写一个程序,找出一个字符串的最小周期的长度。 ### 做法&想法 我们可以枚举答案,然后 $O(n)$ 判断(最坏情况) 显然是个枚举的方法,感觉跑不满,应该还行吧。 中间题目改了!差评!!! 由于之前的输入格式没有字符个数 $n$,所以我是直接 ```cpp scanf("%s",ch); n=strlen(ch); ``` 结果,读入 $n$ 后,我的代码就变成了 ```cpp read(n); for(int i=1;i<=n;i++)cin>>ch[i]; ``` 可以注意到,最开始是从 $0$ 开始编号的写法,后一种是从 $1$ 开始编号的写法,结果我以为没问题,改了之后样例都不测就交了(这个行为可以说是愚蠢至极!哎~) ### 代码 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } char ch[100010]; int n; bool check(int x){ for(register int i=x;i<n;i++) if(ch[i]!=ch[i%x])return false; return true; } int main(){ read(n);scanf("%s",ch); for(register int i=1;i<n;i++) if(check(i)){ printf("%d\n",i); return 0; } cout<<n; return 0; } ``` ## $T_3$ ### 题目描述 >处理完复读机的事,淘淘正想放松一下,口袋里的手机响了,接通后传来了青少年科技中心可人老师的亲切声音,可人老师要求淘淘出一道既能测出选手智力同时又不超出小学数学知识范围的题,这让淘淘犯难了,出什么好呢?淘淘在脑海中将历年的NOIP复赛题想了一遍,突然灵机一动,想到了Hankson的趣味题,淘淘觉得可以将它简化改编一下,变成一道考验小学生的智力问题,改编后的问题如下:给你两个正整数X和Z,其中Z一定是X的倍数,要你求出满足下列两个条件的正整数Y >- 条件1:Z是X和Y的最小公倍数 >- 条件2:在满足条件1的前提下要求Y最小 > >淘淘考虑到小朋友们能来参加本次比赛都不容易,每个人都是各区的精锐之师,因此他在设计测试数据时手下留情了,使得每个小朋友都有大把的分数可拿,当然了,越是聪明的小朋友能拿的分越多! ### 做法&想法 PS:这题折腾了半个多小时,有难度的。 首先,我们将 $X$、$Y$ 和 $Z$ 用数论的形式表示一下。 $$X=\prod\limits_{p_i\in prime}p_i^{a_i}$$ $$Y=\prod\limits_{p_i\in prime}p_i^{b_i}$$ $$Z=\prod\limits_{p_i\in prime}p_i^{\max(a_i,b_i)}$$ 那么, $Z\div X$ 就等于 $$\prod\limits_{p_i\in prime}p_i^{f(a_i,b_i)\times (b_i-a_i)}$$ 其中,$f$ 函数的定义如下: 当 $f(x,y)$ 中,$x>y$ 时,$f(x,y)$ 为 $0$;反之 $x \leq y$ 时,$f(x,y)$ 为 $1$。 **因为我们要 $Y$ 尽量小,所以,当 $a_i\geq b_i$ 时,我们需要让 $b_i=0$** 所以,$Y$ 就等于 $$\prod\limits_{p_i\in prime}p_i^{k(b_i,\max(a_i,b_i))\times (b_i-a_i)}$$ 其中,$k$ 函数的定义如下: 当 $k(x,y)$ 中,$x=y$ 时,$k(x,y)$ 为 $1$;反之 $x \neq y$ 时,$k(x,y)$ 为 $0$。 我们考虑实际意义,上述式子 $Z\div X$ 就是 $Y$ 比 $X$ 多的因子。 举个例子:$X=24,Z=360$ $Z\div X=360\div 24=15=3^1\times 5^1$ $X=24=2^3\times 3^1=2^3\times 3^1\times 5^0$ 所以 $Y=3^{1+1}\times 5^{0+1}=3^2\times 5^1=45$ 当然,$b_i-a_i$ 并不一定 $>$ $a_i$,所以,我们要用 $while$ 来搞。 ### 代码 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } ll gcd(ll x,ll y){ if(x%y==0)return y; return gcd(y,x%y); } int main(){ ll x,y,z;read(x);read(z); y=z/x; while(gcd(x,y)!=1){ int a=gcd(x,y); y*=a;x/=a; }cout<<y; return 0; } ``` ## $T_4$ ### 题目描述 > 2022世界杯马上就要开始了,经过一番激烈角逐,全球32支球队获得了出线资格。世界杯的比赛分为两个阶段,分别为小组赛阶段和淘汰赛阶段,在小组赛阶段32支球队将分成8个小组,每个小组<strong>4支球队进行循环比赛,即每两支球队比赛一次</strong>,每支球队会进行3场比赛,胜得3分,平得1分,输得0分。陶陶和蓝蓝都是足球迷,蓝蓝预测了几次2022世界杯小组赛各个队伍的得分,陶陶想知道这些得分情况是否可能出现? ### 做法&想法 我们可以穷举出所有可能的结果,打表。 ### 代码 代码就不放了 ## $T_5$ ### 题目描述 >淘淘和蓝蓝在星际旅行时来到了瓦尔登星,他们们遇到了这里的原住精灵,意外地发现他们喜欢吃桃子,于是决定将飞船里的桃子分给他们一些。 精灵一共有N只,编号为1到N,第i只精灵需要吃Ai个桃子。现在每只精灵会按1到N的顺序领取桃子,每只精灵可选择淘淘和蓝蓝中的一个人,并向他讨要桃子。但精灵们也会有情绪,假设前面和自己选了同一个人的精灵讨要到的桃子的最大个数是x&#44;如果x大于自己的需求Ai&#44;那么这个精灵会产生x-Ai的不开心值;如果x不大于自己的需求Ai&#44;那么这个精灵不会产生不开心值。 淘淘和蓝蓝想让所有精灵的不开心值总和最小,他们想问你这个总和的最小值是多少? ### 做法&想法 我们可以枚举全排列 ### 代码 我代码还是需要放一下,这个是枚举 $0/1$ 的全排列,我们可以使用**二进制**的方法,就像状压 $dp$。 我认为这种写法比较稳,不会出现爆栈之类的情况。 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } int n,a[100010],v1,v2,ans=INT_MAX; int main(){ read(n); for(int i=1;i<=n;i++)read(a[i]); for(int i=0;i<(1<<n);i++){ v1=0;v2=0;int s=0; for(int j=1;j<=n;j++) if(i&(1<<(j-1))){ v1=max(v1,a[j]); if(v1!=a[j])s+=v1-a[j]; }else{ v2=max(v2,a[j]); if(v2!=a[j])s+=v2-a[j]; } ans=min(ans,s); }cout<<ans; return 0; } ``` 当然 $dfs$ 也是可以的。 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } int n,a[100010],v1,v2,ans=INT_MAX,f[100010]; void dfs(int x){ if(x==n+1){ v1=0;v2=0;int s=0; for(int i=1;i<=n;i++) if(f[i]==0){ v1=max(v1,a[i]); if(v1!=a[i])s+=v1-a[i]; }else{ v2=max(v2,a[i]); if(v2!=a[i])s+=v2-a[i]; } ans=min(s,ans); return; } f[x]=0;dfs(x+1); f[x]=1;dfs(x+1); } int main(){ read(n); for(int i=1;i<=n;i++)read(a[i]); dfs(1); return 0; } ``` 有一说一,$2$ 种写法的代码相似度还是很高的。 ## $T_6$ ### 题目描述 >淘淘来到了一座城市,&nbsp;这座城市有N个景点,共有N - 1条道路连接着彼此,每个景点到另外一个景点有且仅有一条通路,并且一些道路正在维修,是无法经过的。淘淘开始时可以在任意一个景点,他决定在接下来的K-1天中,每天从当前所在的景点不经过正在维修的道路前往另一个景点<span>(他也可以选择逗留在原来的景点,也可以去别的景点,并且不限于相邻的景点,详见样例2解释)&nbsp;</span>,淘淘希望你求出有多少种不同的旅行方案。 方案数可能很大,因此他要求你求出答案对20200621取模的结果,也就是说,如果你求出了答案Ans&#44;请输出Ans % 20200621。 ### 做法&想法 首先是 $80$ 分的做法,我们考虑 $dp$。 $f_{i,j}$ 表示第 $j$ 天到底第 $i$ 个点的方案数,我们还可以使用滚的数组,来滚动掉。 我们发现,这个题目需要我们维护连通块,自然考虑并查集。 我们可以发现,由于每天可以走许多路,所以,$f_{i,j}=size(i)^{j-1}$ 其中,$size$ 函数的定义如下: $size(x)$ 表示 $x$ 所在的连通块节点个数。 ### 代码 $80pts$ ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } const int MOD=20200621; vector<int>v[100010]; ll n,k,f[100010][2],fa[100010],ans; int find(int x){ if(fa[x]==x)return x; return fa[x]=find(fa[x]); } int main(){ read(n);read(k); for(int i=1;i<=n;i++)fa[i]=i; for(int i=1;i<n;i++){ int x,y,z;read(x);read(y);read(z); if(z)fa[find(x)]=find(y); } for(int i=1;i<=n;i++)v[find(i)].push_back(i); for(int i=1;i<=n;i++)f[i][1]=1; for(int j=2;j<=k;j++) for(int i=1;i<=n;i++){ f[i][(j%2)]=0; for(auto k:v[find(i)]) f[i][j%2]=(f[i][j%2]+f[k][(j-1)%2])%MOD; } for(int i=1;i<=n;i++)ans=(ans+f[i][k%2])%MOD; cout<<ans; return 0; } ``` $100pts$ ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } const int MOD=20200621; vector<int>v[100010]; ll n,k,f[100010][2],fa[100010],ans,size[100010]; int find(int x){ if(fa[x]==x)return x; return fa[x]=find(fa[x]); } int pw(int x,int y){ int ans=1; for(;y;y>>=1){ if(y&1)ans=1ll*ans*x%MOD; x=1ll*x*x%MOD; }return ans; } int main(){ read(n);read(k); for(int i=1;i<=n;i++)fa[i]=i,size[i]=1; for(int i=1;i<n;i++){ int x,y,z;read(x);read(y);read(z); if(z){ size[find(x)]+=size[find(y)]; size[find(y)]=0; fa[find(y)]=find(x); } } for(int i=1;i<=n;i++)ans=(ans+pw(size[find(i)],k-1))%MOD; cout<<ans; return 0; } ``` ## $T_7$ ### 题目描述 >淘淘和蓝蓝在玩一款神奇的游戏——主地斗!规则如下: 两位玩家使用一副扑克牌(一张大王,一张小王,其他牌各四张,共54张)中的一些牌参加游戏,每人开始有4张手牌,其余的牌被放在牌堆之中。<strong>所有的手牌和牌堆里牌的顺序和点数都被玩家知晓</strong>。规定扑克牌的点数大小为 **大王=小王**&gt;2&gt;A&gt;K&gt;Q&gt;J&gt;10&gt;9&gt;8&gt;7&gt;6&gt;5&gt;4&gt;3。每局游戏有数轮。每轮先手会在桌面上放置至少一张,至多不超过后手手牌数量的牌(大王和小王除外),后手可以用点数更大的牌叠在桌面上的牌上(也可以选择不叠)。若所有桌面上的牌被叠到,则后手获得此轮胜利,桌面上的牌全部移出游戏,下一轮游戏先手变为后手,后手变为先手。否则先手获得此轮胜利,桌面上的牌全部进入后手的手牌(无视手牌上限),下一轮游戏先手与后手不变。 轮与轮之间,若上一轮的先手的手牌不足4张,将从牌堆顶部拿牌直到手牌为4张或者牌堆被摸空。 若一轮游戏开始前。一名玩家没有手牌而另一名玩家有手牌,那么没有手牌的玩家获得这局游戏的胜利。如果两名玩家都有手牌,但是先手玩家只有大王和小王导致他无法放牌,那么后手玩家获胜。 因为总是赢不了淘淘所以淘气的蓝蓝准备出老千。 他有以下两种出千方式: > >1. 海底捞月:无视手牌上限从牌堆底部摸一张牌。 > >2. 偷天换日:将淘淘的任意一张手牌与自己的任意一张牌对调。 > >出千可以在这局游戏游戏开始前进行,并且为了不被发现,一局游戏只能出千一次。已知两名玩家都会按最优策略(即希望获得本局游戏胜利,下同)出牌,蓝蓝会按最优策略出千。(若不出千便能取得胜利,蓝蓝一定不会出千。若必须出千且两种方式均能取胜,则优先出海底捞月千) > >现在蓝蓝想问你是否能取得胜利。若取得胜利,是否需要出千,出什么千。 ### 做法&想法 如此长的题面当然要骗分啦 ### 代码 骗分($60pts$ 的好成绩) ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } int main(){ int a,b;read(a);read(b); if(a==8&&b==0){puts("Win");puts("No");return 0;} if(a==9&&b==1){puts("Win");puts("Yes");puts("2");return 0;} puts("Win"); puts("No"); return 0; } ``` $100pts$ 略(逃 ## $T_8$ ### 题目描述 >淘淘和蓝蓝喜欢“归整”的序列。长度为N的非负整数序列A是“归整”的,当且仅当A的任意连续K个元素的和都是S。 > >我们可以通过修改一些元素来使一个序列变成“归整”的。可以把A的任意元素修改成0到S之间(包含0和S)的任意整数。淘淘和蓝蓝想知道至少修改多少个元素才能使序列A变成“归整”的,于是找到你来帮助他们。 ### 做法&想法 先考虑一个性质: $a_i=a_{i\bmod k}$ 为什么? 首先,$a_i+a_{i+1}\cdots +a_{i+k-1}=a_{i+1}\cdots +a_{i+k-1}+a_{i+k}-S$ 所以,$a_i=a_{i+k}$ 之后我们就可以开心的 $dp$ 了,我们 $f_{i,j}$ 表示前 $i$ 个数和为 $j$ 的方案数。 我们可以枚举第 $i$ 个数,假设它是 $k$,那么 $f_{i,j}=f_{i-1.j-k}+h_{i,k}$ 其中,$h$ 函数的定义如下: $h_{i,j}$ 表示下标和 $i$ 在模 $k$ 意义下同余的数中不为 $k$ 的数的个数。 这东西我们可以 $O(n^2)$ 预处理出来。 ### 代码 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } int a[310],ans,f[310][310],h[310][310]; int main(){ memset(f,0x3f,sizeof(f)); int n,k,s; read(n);read(k);read(s);ans=n; for(int i=1;i<=n;i++)read(a[i]); for(int i=0;i<=s;i++) for(int j=1;j<=n;j++) h[(j-1)%k+1][i]+=(a[j]!=i); f[0][0]=0; for(int i=1;i<=k;i++) for(int j=0;j<=s;j++) for(int k=0;k<=j;k++) f[i][j]=min(f[i][j],f[i-1][j-k]+h[i][k]); cout<<f[k][s]; return 0; } ``` # $Day\ 2$ 哎,发现自己炸了,$T_2$ 挂成 $10pts$,$T_4$ 挂成 $30pts$,$T_8$ 代码中的第 $23$ 行 `h[i][k]` 手滑打成 `h[i][j]` 关键是还能过样例!气死我也…… $3$ 小时的时间是真的短,最后只拍了拍感觉最不稳的 $2$ 题(结果都没发现错误) 哎,$8$ 题挂了 $3$ 题,我拍题怎么就没拍到这 $3$ 题中的一题呢? 我也不知道。。。 放一放对拍的代码留作纪念吧。。。 桃子的对拍 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } int n,a[100010],v1,v2,ans=INT_MAX,f[100010],ans1=INT_MAX; void dfs(int x){ if(x==n+1){ v1=0;v2=0;int s=0; for(int i=1;i<=n;i++){ if(f[i]==0){ v1=max(v1,a[i]); if(v1!=a[i])s+=v1-a[i]; }else{ v2=max(v2,a[i]); if(v2!=a[i])s+=v2-a[i]; } } ans1=min(s,ans1); return; } f[x]=0; dfs(x+1); f[x]=1; dfs(x+1); } int main(){int s=0; while(1){n=8; for(int i=1;i<=n;i++)a[i]=rand()%100+1; dfs(1); for(int i=0;i<(1<<n);i++){ v1=0;v2=0;int s=0; for(int j=1;j<=n;j++){ if(i&(1<<(j-1))){ v1=max(v1,a[j]); if(v1!=a[j])s+=v1-a[j]; }else{ v2=max(v2,a[j]); if(v2!=a[j])s+=v2-a[j]; } } ans=min(ans,s); } if(ans1!=ans){ for(int i=1;i<=n;i++)cout<<a[i]; return 0; }printf("%d\n",++s); } return 0; } ``` 复读机的对拍: ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; template<typename T>inline void read(T &FF){ T RR=1;FF=0;char CH=getchar(); for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1; for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48); FF*=RR; } ll gcd(ll x,ll y){ if(x%y==0)return y; return gcd(y,x%y); } ll ans1(ll x,ll z){ ll y=z/x; while(gcd(x,y)!=1){ int a=gcd(x,y); y*=a;x/=a; } return y; } ll ans2(ll x,ll z){ for(int i=1;i<=z;i++){ if(x*i/gcd(i,x)==z)return i; } } int main(){ int s=0; while(1){ ll x,y,z;x=rand()%100+1;z=(rand()%100+1)*x; // cout<<x<<" "<<z<<" "<<ans2(x,z)<<endl; if(ans1(x,z)!=ans2(x,z)){ cout<<x<<" "<<z<<" "<<ans2(x,z)<<endl; return 0; }printf("%d\n",++s); } return 0; } ``` # $Day\ ?$ 惊喜地发现自己炸成这样还有 $RK2$!真是奇迹,看来我还是不错的QAQ