正题
A - Prefix and Suffix
题目要求找到前缀和后缀的最大匹配, n n n这么小,暴力枚举即可。
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n;
char a[N],b[N];
int main(){
scanf("%d",&n);
scanf("%s",a+1);
scanf("%s",b+1);
int mmax=0;
for(int t=1;t<=n;t++){
bool tf=true;
for(int i=1;i<=t;i++) if(a[n-t+i]!=b[i]){tf=false;break;}
if(tf) mmax=t;
}
printf("%d\n",2*n-mmax);
}
B - Median Pyramid Easy
构造题,考虑如何构造,首先假如
x
x
x为最小值或者最大值肯定无解,因为倒数第二层这两个值就会消失。
然后考虑在倒数第一层的中间,放上
x
−
1
,
x
,
x
+
1
x-1,x,x+1
x−1,x,x+1三个值,可以证明,其他的位置不论怎么放,最后顶端的一定是
x
x
x,我们一层一层来归纳证明。
先考虑倒数第二层,中间的位置必然是
x
x
x,左边这个位置要么是
x
x
x,要么是
x
−
1
x-1
x−1,右边这个位置要么是
x
x
x,要么是
x
+
1
x+1
x+1。那么继续往上也是一样的。
#include<bits/stdc++.h>
using namespace std;
int n,x;
int main(){
scanf("%d %d",&n,&x);
if(x==1 || x==2*n-1){printf("No\n");return 0;}
printf("Yes\n");
int now=1;
for(int i=1;i<=n-2;i++){
if(now==x-1) now=x+2;
printf("%d\n",now);now++;
}
printf("%d\n%d\n%d\n",x-1,x,x+1);
for(int i=1;i<=n-2;i++){
if(now==x-1) now=x+2;
printf("%d\n",now);now++;
}
}
C - Rabbit Exercise
考虑
j
j
j跳了之后的位置,要么是
2
∗
a
j
−
1
−
a
j
2*a_{j-1}-a_j
2∗aj−1−aj,要么是
2
∗
a
j
+
1
−
a
j
2*a_{j+1}-a_j
2∗aj+1−aj,根据期望的离散性,可以知道,直接将新的
a
j
a_j
aj当成
a
j
−
1
+
a
j
+
1
−
a
j
a_{j-1}+a_{j+1}-a_j
aj−1+aj+1−aj即可。
这样好像什么都看不出来,妙在下一步。
考虑差分数组
b
i
b_i
bi,可以发现如果操作了
j
j
j,那么相当于交换了
b
j
,
b
j
+
1
b_j,b_{j+1}
bj,bj+1。
m
m
m次交换会形成一个置换,置换是轮换的拼接,我们处理每一个轮换就可以知道第
k
k
k步是什么了,最后求个前缀和就是
a
a
a数组了,可以发现小数部分没有用?确实
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
long long a[N],b[N];
int n,m,w[N];
long long k;
bool vis[N];
vector<int> V;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),w[i]=i;
scanf("%d %lld",&m,&k);
int x;
for(int i=1;i<=m;i++) scanf("%d",&x),swap(w[x],w[x+1]);
for(int i=1;i<=n;i++) if(!vis[i]){
int now=i;V.resize(0);
while(!vis[now]){
vis[now]=true;
V.push_back(now);
now=w[now];
}
int tmp=k%V.size();
for(int j=0;j<V.size();j++)
w[V[j]]=V[(j+tmp)%V.size()];
}
for(int i=n;i>=1;i--) a[i]-=a[i-1];
for(int i=1;i<=n;i++) b[i]=a[w[i]],b[i]+=b[i-1],printf("%lld.0\n",b[i]);
}
D - Median Pyramid Hard
首先考虑二分,转化为一个
01
01
01序列想要知道最上面的节点是什么。
对于每一个连续且长度不为
1
1
1的
01
01
01段单独考虑。
哪边不与另一个连续段相接,层数每次上升一次,这样的
01
01
01段长度就会向其方向拓展
1
1
1。
只需要证明拓展出来的一位为什么正确就好了,容易发现这一位的左下角和右下角必是
01
01
01段的数字。(其中一边就是原来的
01
01
01段,另一边因为保证了不与另一个
01
01
01段相接,所以也是同样的颜色。)
同样也可以证明为什么不会拓展
2
2
2。
那么最后就剩下最靠近中心的两个
01
01
01段,如果颜色不同,那么在靠在一起的时候都不会向相对的一边拓展,垂直上升,所以我们只需要找到最靠近中心的
01
01
01段即可。
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int n,a[N];
bool check(int x){
int mmin=1e9,tot=1;
bool tf=(a[1]<=x);
for(int i=2;i<=n;i++){
if((a[i]<=x)^(a[i-1]<=x)^1) tot++;
else tot=1;
if(tot>1) mmin=n-i,tf=(a[i]<=x);
}
tot=1;
for(int i=2*n-2;i>=n;i--){
if((a[i]<=x)^(a[i+1]<=x)^1) tot++;
else tot=1;
if(tot>1 && i-n<mmin) mmin=i-n,tf=(a[i]<=x);
}
return tf;
}
int main(){
scanf("%d",&n);
for(int i=1;i<2*n;i++) scanf("%d",&a[i]);
int l=1,r=2*n-1,ans=0;
while(l<=r){
int mid=(l+r)/2;
if(check(mid)) r=(ans=mid)-1;
else l=mid+1;
}
printf("%d\n",ans);
}
E - Rotate 3x3
首先可以发现每一列可以简化为一个值,这个值表示原本属于那一列,并且是否有反转,有反转就为
−
-
−,没反转就为
+
+
+,考虑一次操作会带来什么影响,即交换相隔一位的两个位置的值,并且让其三个值同时取反。
那么首先判断不可行的条件就是奇偶性不正确。
考虑交换两个奇偶性相同的位置
x
,
y
x,y
x,y并且取反对另一奇偶性位置带来的影响,发现这样的操作可以给奇数个另一奇偶性位置取反,证明先考虑交换
x
,
y
x,y
x,y可以给一个另一奇偶性位置取反,因为如果要使得
z
z
z取反,可以先让
x
x
x交换到
z
z
z的一边,再让
y
y
y交换到
z
z
z的另外一边,两者再进行交换,可以发现路径上的其他值都被取反了两次,没有变换,而
x
,
y
,
z
x,y,z
x,y,z都被取反了奇数次,那么连续交换
x
,
y
x,y
x,y奇数次就可以让奇数个这样的位置取反。
另外一个位置取反两次也是不变的。所以我们记录一下奇位置交换位置的次数奇偶性,偶位置交换位置的次数奇偶性。然后看看取反次数的奇偶性是否与另一奇偶性位置的交换位置次数奇偶性相同即可。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,a[N][3],b[N],pos[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i][0]);
for(int i=1;i<=n;i++) scanf("%d",&a[i][1]);
for(int i=1;i<=n;i++) scanf("%d",&a[i][2]);
bool tf=false;
int t[2];t[0]=t[1]=0;
for(int i=1;i<=n;i++){
if(a[i][0]+1==a[i][1] && a[i][1]+1==a[i][2]){
if(a[i][2]%3==0) b[i]=a[i][2]/3,pos[a[i][2]/3]=i;
else {tf=true;break;}
}
else if(a[i][0]==a[i][1]+1 && a[i][1]==a[i][2]+1){
if(a[i][0]%3==0) b[i]=-a[i][0]/3,pos[a[i][0]/3]=i;
else {tf=true;break;}
}
else {tf=true;break;}
if((abs(b[i])&1)^(i&1)) {tf=true;break;}
}
for(int i=1;i<=n;i++){
if(b[i]!=i && b[i]+i!=0){
t[i&1]++;
b[i]=-b[i];b[pos[i]]=-b[pos[i]];
swap(b[i],b[pos[i]]);
swap(pos[i],pos[abs(b[pos[i]])]);
}
if(b[i]<0) t[(i&1)^1]--;
}
if(t[0]%2!=0 || t[1]%2!=0) tf=true;
if(tf) {printf("No\n");return 0;}
else printf("Yes\n");
}
F - Blackout
相当于图中如果存在
(
x
,
y
)
,
(
y
,
z
)
(x,y),(y,z)
(x,y),(y,z),就连一条
z
,
x
z,x
z,x。
考虑对于每一个联通块单独处理,并且对于该图三染色,有向边怎么三染色?倒着走的时候-1就可以。
如果染色成功并且存在三种颜色,那么可以用归纳法来证明图中
(
0
,
1
)
,
(
1
,
2
)
,
(
2
,
0
)
(0,1),(1,2),(2,0)
(0,1),(1,2),(2,0)都有边,读者自证不难。
如果染色成功并且颜色没有三种,说明构不成长度为
2
2
2的路径,边集就是原来的边集。
如果染色不成功,说明有一个点被染成了两种颜色,用第一个讨论中的归纳法可以证明存在自环,如果一个点存在自环了,可以发现,这张图会被拓展为完全图。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct edge{
int y,nex,c;
}s[N<<1];
int first[N],len=0,n,m,d[N],tot=0,num[3],e=0;
bool we;
void ins(int x,int y,int c){
s[++len]=(edge){y,first[x],c};first[x]=len;
}
void dfs(int x,int c){
d[x]=c;num[c]++;
tot|=(1<<c);
for(int i=first[x];i!=0;i=s[i].nex){
if(s[i].c==1) e++;
int y=s[i].y,t=(s[i].c+3+c)%3;
if(d[y]!=-1 && d[y]!=t) we=false;
if(d[y]==-1) dfs(y,t);
}
}
int main(){
scanf("%d %d",&n,&m);
int x,y;
for(int i=1;i<=m;i++){
scanf("%d %d",&x,&y);
ins(x,y,1),ins(y,x,-1);
}
memset(d,-1,sizeof(d));
long long ans=0;
for(int i=1;i<=n;i++) if(d[i]==-1){
tot=0;we=true;num[0]=num[1]=num[2]=0;e=0;
dfs(i,0);
if(we){
if(tot==7) ans+=1ll*num[0]*num[1]+1ll*num[1]*num[2]+1ll*num[2]*num[0];
else ans+=e;
}
else ans+=1ll*(num[0]+num[1]+num[2])*(num[0]+num[1]+num[2]);
}
printf("%lld\n",ans);
}