直接开始讲题吧。
比赛链接在这:阿巴
A - Addition
只需要看看奇数的个数是否为奇数即可。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N],n,t[2];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),t[a[i]&1]++;
printf(t[1]&1?"NO\n":"YES\n");
}
B - Boxes
想了一会
首先考虑差分,操作就变成给全局
+
1
+1
+1之后再给某个位置
−
n
-n
−n。
将差分数组
b
b
b处理出来,观察全局要加多少个
1
1
1。
显然如果
b
i
b_i
bi是正的那么就要加至少
b
i
b_i
bi个1,否则就加到与
b
i
b_i
bi同余的那个数就可以了。
记这个全局加的次数为
m
m
a
x
mmax
mmax,试着加一下,若出现
m
o
d
n
≠
0
\mod n\not=0
modn=0的情况,那么非法。可以处理出来每一个点对应的操作次数,操作次数总和是
t
o
t
tot
tot,若不相等,那么无论怎么操作这两个东西都不会相等,非法。
最后还要看看这样加完之后序列在大小上是否合法,只需要考虑其中一位的取值就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,a[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int tmp=a[n],mmax=0;
for(int i=n;i>=1;i--) a[i]-=a[i-1];a[1]-=tmp;
for(int i=1;i<=n;i++){
a[i]=-a[i];
if(a[i]<0) mmax=max(mmax,-a[i]);
else mmax=max(mmax,(a[i]%n==0?0:n-a[i]%n));
}
long long tot=0,ans=0;
bool tf=true;
for(int i=1;i<=n;i++) {
a[i]+=mmax;
if(a[i]%n) {tf=false;break;}
tot+=a[i]/n;
ans+=1ll*a[i]/n*((n-i)%n+1);
}
if(tot!=mmax || tmp<ans || ans<=tmp && (tmp-ans)%(1ll*(1+n)*n/2)) tf=false;
printf(tf?"YES\n":"NO\n");
}
C - Cleaning
考虑
d
f
s
dfs
dfs,每一个子树会返回上来一下需要解决的路径数。
然后根据当前节点的
a
[
x
]
a[x]
a[x]可以解出来当前节点返回的路径数量,剩下的两两配对。
若配对数为
d
d
d,那么两两配对要满足不存在一个子树的路径数
>
d
>d
>d,多出来的直接算到返回的中。
中途若有不合法的情况直接输出非法即可。
根节点不能有返回值。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
struct edge{
int y,nex;
}s[N<<1];
int n,first[N],len,a[N],in[N];
bool tf=false;
void ins(int x,int y){
s[++len]=(edge){y,first[x]};first[x]=len;
}
int dfs(int x,int fa){
if(in[x]==1) return a[x];
vector<int> V;
long long tot=0;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
int tmp=dfs(s[i].y,x);
tot+=tmp;
V.push_back(tmp);
}
if(tot>2*a[x]) {printf("NO\n");exit(0);}
tot=2*a[x]-tot;
int d=a[x]-tot;
if(d<0) {printf("NO\n");exit(0);}
long long ans=0;
for(int i=0;i<V.size();i++) if(V[i]>d) ans+=V[i]-d;
if(ans>tot) {printf("NO\n");exit(0);}
return tot;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int x,y;
for(int i=1;i<n;i++) {
scanf("%d %d",&x,&y);
in[x]++;in[y]++;
ins(x,y),ins(y,x);
}
if(n==1) printf("NO\n");
else if(n==2) printf(a[1]==a[2]?"YES\n":"NO\n");
else{
int rt=0;
for(int i=1;i<=n;i++) if(in[i]>1) {rt=i;break;}
int d=dfs(rt,0);
printf(d?"NO\n":"YES\n");
}
}
D - Decrementing
直接从
g
c
d
gcd
gcd入手好像不太行,考虑奇偶性。
如果存在奇数个偶数,那么先手必胜:
首先发现当前的局面至少存在一个奇数,否则
g
c
d
gcd
gcd不为
1
1
1。
先将其中一个偶数操作为奇数,操作之后的
g
c
d
gcd
gcd也不可能为偶数,不会改变奇偶性,此时局面至少存在两个奇数。
若后手将其中一个奇数
−
1
-1
−1变成偶数,由于至少存在两个奇数,所以
g
c
d
gcd
gcd也不可能为偶数,不会改变奇偶性,先手只需将这个偶数
−
1
-1
−1即可。
若后手将其中一个偶数
−
1
-1
−1,则先手将另一个偶数
−
1
-1
−1。
最终后手剩下
0
0
0个偶数,且奇数全为
1
1
1, 输掉了游戏。
若存在偶数个偶数,而且奇数个数
>
1
>1
>1,那么先手必败,正确性同上。
只剩下一种情况:
存在偶数个偶数并且奇数个数
=
1
=1
=1。
显然不可能操作偶数,否则后手可以操作另一个偶数,必败。
如果这个奇数不为
1
1
1,那么操作它,观察下一个局面是否必败或者必胜即可。
否则必败。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N],n;
int gcd(int x,int y){
return y==0?x:gcd(y,x%y);
}
bool gs(){
int tot=0;
for(int i=1;i<=n;i++) tot+=(a[i]&1);
if((n-tot)&1) return true;
if(tot>1) return false;
tot=0;
for(int i=1;i<=n;i++) if(a[i]==1) return false;
for(int i=1;i<=n;i++) a[i]-=(a[i]&1),tot=gcd(tot,a[i]);
for(int i=1;i<=n;i++) a[i]/=tot;
return gs()^true;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
printf(gs()?"First":"Second");
}
E - Rearranging
首先很显然的一个性质:两个不互质的数的相对关系不会改变。
可以联想到给不互质的数连一条无向边,先手给其定向,后手要使得拓扑序最大。
对于每一个联通块而言,肯定使得最小点为第一个点,后面考虑
d
f
s
dfs
dfs。
贪心地从小到大遍历有连边而且没有遍历过的点,并给这条边定向。
合并的时候按照拓扑序最大来合并就可以了。
为了方便操作,博主倒序存储了这个列表,方便在最前面插入一个点。
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int a[N],n,tmp[N];
vector<int> ans;
bool d[N][N];
bool vis[N],we[N];
int gcd(int x,int y){return y==0?x:gcd(y,x%y);}
void merge(vector<int>&A,vector<int> B){
tmp[0]=0;
int a=A.size()-1,b=B.size()-1;
while(a>=0 && b>=0){
if(A[a]>B[b]) tmp[++tmp[0]]=A[a--];
else tmp[++tmp[0]]=B[b--];
}
while(a>=0) tmp[++tmp[0]]=A[a--];
while(b>=0) tmp[++tmp[0]]=B[b--];
A.resize(tmp[0]);
for(int i=tmp[0]-1;i>=0;i--) A[i]=tmp[tmp[0]-i];
}
vector<int> gs(int x){
vis[x]=true;
vector<int> A;
for(int i=1;i<=n;i++) if(!vis[i] && d[x][i])
merge(A,gs(i));
A.push_back(x);
return A;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]=(gcd(a[i],a[j])!=1);
for(int i=1;i<=n;i++) if(!vis[i]) merge(ans,gs(i));
for(int i=n-1;i>=0;i--) printf("%d ",a[ans[i]]);
}
F - Tree Game
从当前点开始
d
f
s
dfs
dfs。
肯定不会走
a
[
y
]
>
=
a
[
x
]
a[y]>=a[x]
a[y]>=a[x]的点,否则来回横跳就可以把自己消耗完。
那么肯定是走
a
[
y
]
<
a
[
x
]
a[y]<a[x]
a[y]<a[x]的点,观察是否后手必败即可。
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
struct edge{
int y,nex;
}s[N<<1];
int first[N],len;
int n,a[N];
void ins(int x,int y){
s[++len]=(edge){y,first[x]};first[x]=len;
}
bool dfs(int x){
for(int i=first[x];i!=0;i=s[i].nex) if(a[s[i].y]<a[x] && !dfs(s[i].y))
return true;
return false;
}
int main(){
scanf("%d",&n);
int x,y;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
for(int i=1;i<=n;i++) if(dfs(i)) printf("%d ",i);
}