把这场忘了。。官方也迟迟不发题解
比赛链接
出题人题解
A 小红的字符串生成
思路:
枚举四种字符串打印出来即可,为了防止重复可以用set先去一下重。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
set<string> S;
string a,b;
int main(){
cin>>a>>b;
S.insert(a);
S.insert(b);
S.insert(a+b);
S.insert(b+a);
cout<<S.size()<<endl;
for(auto x:S)
cout<<x<<endl;
return 0;
}
B 小红的非排列构造
思路:
如果它本身就不是一个排列,那么就不需要修改,如果是排列,我们就换一个不是排列中的数进去,那么它就一定不是排列了。
判定是否为排列的方法很多,我是先排序再去重,然后看一下两头的数是不是1和n就行了
code:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],cnt;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
cnt=unique(a+1,a+n+1)-a-1;
if(cnt==n && a[1]==1 && a[n]==n)printf("1\n1 1000000");
else printf("0");
return 0;
}
C 小红的数字拆解
思路:
看半天没看懂,原来是切割数位啊😅
观察一下不难发现,如果想要切割出来的数是个偶数,那么就需要个数为偶数,高位无所谓。而我们要尽可能切出来最多偶数,所以奇数一定和和它右边遇到的第一个偶数进行结合。
切割方法找到了,现在问题变成了怎么存,怎么排序。一般想法是用string存,并用string内置的排序办法。但是string排序默认是字典序排序而不是数字的先比数位。于是用结构体存一个string,并重载一下排序办法就可以了。
code:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <string>
using namespace std;
string str,t;
struct Str{
string s;
Str(string x):s(x){};
bool operator<(const Str x)const{
if(s.size()==x.s.size())return s<x.s;
return s.size()<x.s.size();
}
};
multiset<Str> S;
int main(){
cin>>str;
for(auto x:str){
t+=x;
if(~(x-'0')&1){
S.insert(Str(t));
t.clear();
}
}
for(auto x:S)
cout<<x.s<<endl;
return 0;
}
D 小红的陡峭值
思路:
手玩一会不难发现:中间的 0 0 0 都是没用的。
如果中间的 0 0 0 都取两边其一的数,那么中间的 0 0 0 就可以看作没有。而如果都不取,差值势必大于1,直接就不满足条件了。因此为了满足条件,中间的 0 0 0 就只能取一边的数,就相当于没用。
现在有用的只有两端的 0 0 0。我们用两个 b o o l bool bool 变量 f 1 , f 2 f1,f2 f1,f2 记录一下开头和结尾有无 0 0 0,然后直接把原序列中所有的 0 0 0 全部去掉(或者直接先替换成一端的数)。问题变成了现在要如何填数?
发现需要求一下中间的数的陡峭值是多少,如果是 0 0 0,就需要两端的 0 0 0 来补,如果是 1 1 1,就不需要两端有 0 0 0,如果大于 1 1 1,直接无解。
code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],cnt;
int main(){
cin>>n;
if(n==1){
printf("-1");
return 0;
}
int flag=0;
for(int i=1,t;i<=n;i++){
cin>>a[i];
if(a[i]==0){
if(i==1)flag=1;
else if(i==n)flag=2;//两端有一端有就行了,所以用了一个变量
a[i]=a[i-1];
}
}
if(a[n]==0)a[n]=5;
for(int i=n;i>=1;i--)
if(a[i]==0)
a[i]=a[i+1];
int tot=0;
for(int i=2;i<=n;i++)
tot+=abs(a[i]-a[i-1]);
if(tot==0 && flag){
if(flag==1)a[1]=(a[2]==1)?2:a[2]-1;
else if(flag==2)a[n]=(a[n-1]==1)?2:a[n-1]-1;
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
}
else if(tot==1){
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
}
else printf("-1");
return 0;
}
E 小红的树形 dp
思路:
一开始的思路是:把一个点钦定为根节点,那么每个点的深度就确定了,如果某个点的值确定,那么对应奇数和偶数深度的点就确定了,然后巴拉巴拉。
不过不用那么麻烦,因为一个点确定了,其他点就随之确定了,因此我们直接遍历一下有没有d或p,如果有,就以它为根节点开始往下填数,如果出现了冲突的点就无解。如果没有d或p,就随便填了。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1e5+5;
int n;
string color;
int head[maxn],cnt;
struct edge{
int v,nxt;
}e[maxn<<1];
void add(int u,int v){
e[++cnt].v=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int u,int rt){
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].v;
if(v==rt)continue;
if(color[v]==color[u]){
printf("-1");
exit(0);
}
color[v]=(color[u]=='d')?'p':'d';
dfs(v,u);
}
}
int main(){
cin>>n>>color;
color=" "+color;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
add(u,v);
add(v,u);
}
if(color.find_first_of("dp",1)!=string::npos)dfs(color.find_first_of("dp",1),-1);
else {
color[1]='d';
dfs(1,-1);
}
cout<<color.substr(1);
return 0;
}
F 小红的矩阵构造
思路:
就是很固定的构造方式,官方讲解讲的已经很明白了,在视频一小时九分那里。大概就是这个样子:
注意 n , m ≥ 4 n,m\ge 4 n,m≥4 还 x x x 是偶数。
code:
#include <iostream>
#include <cstdio>
using namespace std;
int n,m,x;
int a[5][5];
int main(){
cin>>n>>m>>x;
if(x==2)cout<<-1<<endl;
else if(x%4==0){
a[1][1]=a[1][2]=a[2][1]=a[2][2]=x/4;
for(int i=1;i<=n;i++,puts(""))
for(int j=1;j<=m;j++){
if(i>4 || j>4)printf("0 ");
else printf("%d ",a[i][j]);
}
}
else {
x-=6;
a[1][1]=a[1][2]=a[2][1]=a[2][2]=x/4;
a[3][2]=a[2][3]=a[3][3]=a[2][4]=a[4][2]=a[4][4]=1;
for(int i=1;i<=n;i++,puts(""))
for(int j=1;j<=m;j++){
if(i>4 || j>4)printf("0 ");
else printf("%d ",a[i][j]);
}
}
return 0;
}
G 小红的元素交换
思路:
用到了置换环这个小知识点。将排列打乱,然后给 a i → i a_i\rightarrow i ai→i 连一条边,连完之后就会出现多个环,只出现环的原因是:如果将每个位置看作一个点,每个点都只有一个入度和一个出度。而且环上每个位置上的数的正确位置都是它指向的下一个位置(废话),也就是环上每个位置的数都只偏移了一位。
根据这个思路,可以把问题转化为,多个置换环怎么把数归位。假设环长为 a a a,发现如果环中两种颜色都存在,那么它可以通过交换 a − 1 a-1 a−1 次把所有数归位。
归位方法是:环上每一段连续的白色或黑色的最后一个标记一下,都先不动,然后从中随便取一个开始向后给路上的每个点进行归位,然后遇到下一个被标记的点,让它接着向下交换,换一遍后剩下的就是黑白黑白颜色交替的颜色没有归位了,再随便找一个,把它和在它的正确位置上的点交换位置(因为是黑白交替,它下一个位置的点一定和它颜色不同,所以一定是可以交换的),然后换出来的点以此类推和正确位置上的点交换,最后就能换好,因为每次交换都会有一个点被放入正确位置,而最后一次交换两个点会同时归位,所以一共需要 a − 1 a-1 a−1 次交换。
如果是纯色的环,就需要在别处借一个异色过来,然后再还回去,这就需要 a + 1 a+1 a+1 次交换。
但是如果有两个不同纯色的环,它们可以先交换其中一个点,两边都排好序后再换回来,这样两边都只需要 a a a 次交换就可以了。
所以这个题我们需要统计每个环是同色还是异色,如果是异色就加
环长
−
1
环长-1
环长−1,否则加
环长
+
1
环长+1
环长+1。再统计纯黑和纯白分别有几个,最后再减去两值小的*2就行了。啊嘞,好像不是黑色是红色
code:
#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn];
string color;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
cin>>color;
color=" "+color;
int ans=0,w=0,r=0;
vector<bool> vis(n+5,false);
for(int i=1;i<=n;i++){
int cnt=0;
bool f1=false,f2=false;//有红 有白
if(!vis[i]){
vis[i]=true;
cnt++;
if(color[i]=='W')f1=true;
else f2=true;
for(int p=a[i];p!=i;p=a[p]){
vis[p]=true;
cnt++;
if(color[p]=='W')f1=true;
else f2=true;
}
if(cnt==1)continue;
else if(f1 && f2)ans+=cnt-1;
else {
ans+=cnt+1;
if(f1)w++;
else r++;
}
}
}
if(ans==0)cout<<0<<endl;
else if(color.find("W")==string::npos || color.find("R")==string::npos)cout<<-1<<endl;
else cout<<ans-min(w,r)*2<<endl;
return 0;
}