正题
A - Simple Calculator
根据绝对值来决定加还是变成负值之后加就可以了。
#include<bits/stdc++.h>
using namespace std;
int A,B,ans=0;
int main(){
scanf("%d %d",&A,&B);
if(abs(A)<abs(B)){
if(A<0) ans++,A=-A;
ans+=abs(B)-abs(A);
A+=abs(B)-abs(A);
}
else if(abs(A)>abs(B)){
if(A>0) ans++,A=-A;
ans+=abs(A)-abs(B);
A+=abs(A)-abs(B);
}
if(A!=B) ans++;
printf("%d\n",ans);
}
B - Contiguous Repainting
既然操作了,那么就考虑最后一次操作在那里,另外的位置想选想不选都可以。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,k,a[N];
long long f[N],g[N],sum[N];
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;i++) f[i]=f[i-1]+max(a[i],0);
for(int i=n;i>=1;i--) g[i]=g[i+1]+max(a[i],0);
long long ans=0;
for(int i=1;i<=n-k+1;i++)
ans=max(ans,f[i-1]+g[i+k]+max(sum[i+k-1]-sum[i-1],0ll));
printf("%lld\n",ans);
}
C - Tetromino Tiling
发现只有
1
,
2
,
4
,
5
1,2,4,5
1,2,4,5有用,其他的必定会多出两个头没办法消除。
2
2
2就不用考虑了,直接加到答案里面。
1
,
4
,
5
1,4,5
1,4,5可以组合成一个
2
×
(
2
×
3
)
2\times (2\times 3)
2×(2×3)的。
自己也可以跟自己组合成一个
2
×
(
2
×
2
)
2\times (2\times 2)
2×(2×2)的。
那么我们让每一个都至少留一个出来先,然后考虑是否都剩下了,而且
=
2
= 2
=2的是否小于等于
1
1
1个,如果是的话,那么就组成一个
2
×
(
2
×
3
)
2\times (2\times 3)
2×(2×3)的,否则就让
=
2
=2
=2的自己组成
2
×
(
2
×
2
)
2\times (2\times 2)
2×(2×2)的。
#include<bits/stdc++.h>
using namespace std;
int a,b,c,d,e,f,g;
long long ans=0;
int main(){
scanf("%d %d %d %d %d %d %d",&a,&b,&c,&d,&e,&f,&g);
ans+=b;
ans+=(a-1)/2*2;a=(a-1)%2+1;
ans+=(d-1)/2*2;d=(d-1)%2+1;
ans+=(e-1)/2*2;e=(e-1)%2+1;
int tot=(a==2)+(d==2)+(e==2);
if(tot<=1 && a && d && e) ans+=(a && d && e)*3;
else ans+=tot*2;
printf("%lld\n",ans);
}
D - K-th K
根据位置要求来贪心放就可以了,使用堆来维护。
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n,a[N],d[N*N],las[N];
priority_queue<pair<int,int> > qs;
pair<int,int> X;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
d[a[i]]=i;
if(i>1) qs.push(make_pair(-a[i],i)),las[i]=i-1;
}
bool tf=true;
for(int i=1;i<=n*n;i++){
if(d[i]){
if(las[d[i]]) {tf=false;break;}
if(d[i]!=n) qs.push(make_pair(-n*n,d[i])),las[d[i]]=n-d[i];
}
else {
if(qs.empty()){tf=false;break;}
X=qs.top();
las[X.second]--;
d[i]=X.second;
if(las[X.second]==0) qs.pop();
}
}
if(!tf) printf("No\n");
else{
printf("Yes\n");
for(int i=1;i<=n*n;i++) printf("%d ",d[i]);
}
}
E - Next or Nextnext
很容易先考虑答案的置换,再考虑
a
i
a_i
ai在上面的位置,但是这样考虑很难算。
正难则反。
考虑对于
a
i
a_i
ai来说,是多个基环内向树。
对于单纯的环来说,可以发现让两个相等长度的环交错套在一起是可行的,自己形成一个单独的环也可以,这部分可以使用组合数暴力算出,注意当环长为奇数的时候,自己套在一起有两种方案,因为奇环上不断
+
2
+2
+2也是可以遍历所有位置的。
对于基环上长出一些边的点来说,这些点形成的一定是一条链,不然无解。
如果是一条链的话,观察一下是否可以将它交错插入环中,如果距离上一个有出边点的长度为
l
l
l,当前链的长度为
l
e
n
len
len,若
l
e
n
>
l
len>l
len>l,那么无法插入,若
l
e
n
=
l
len=l
len=l,那么刚好可以插入,若
l
e
n
<
l
len<l
len<l,说明还可以先空出一个点出来不插入,即存在两种方案。
那么根据乘法原理乘一乘就做完了。
#include<bits/stdc++.h>
using namespace std;
const int N=100010,mod=1000000007;
struct edge{
int y,nex;
}s[N];
int first[N],len=0,a[N],n,d[N],tf[N],tag,tot[N];
int fac[N],inv[N];
vector<int> V;
int ksm(int x,int t){
int tot=1;
while(t){
if(t&1) tot=1ll*tot*x%mod;
x=1ll*x*x%mod;
t/=2;
}
return tot;
}
void ad(int&x,int y){x=(x+y>=mod)?(x+y-mod):(x+y);}
void ins(int x,int y){
d[x]++;s[++len]=(edge){y,first[x]};first[x]=len;
}
void dfs(int x){
if(tf[x]){
if(tf[x]==tag) V.push_back(x);
return ;
}
tf[x]=tag;
dfs(a[x]);
if(V.size()){
if(tag){
if(V[0]!=x) V.push_back(x);
else tag=0;
}
}
}
int gas(int x,int fa){
if(d[x]>=2) return 1e9;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa)
return gas(s[i].y,x)+1;
return 0;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),ins(a[i],i);
int ans=1;
for(int i=1;i<=n;i++) if(!tf[i]){
V.clear();
tag=i,dfs(i);
if(!V.size()) continue;
int las=0;
for(int j=0;j<V.size();j++) if(d[V[j]]==1) las++;
else break;
if(las==V.size()) {tot[las]++;continue;}las++;
for(int j=V.size()-1;j>=0;j--) if(d[V[j]]>1){
if(d[V[j]]>2) {ans=0;break;}
int nex=s[first[V[j]]].y;
if(nex==(j==V.size()-1?V[0]:V[j+1])) nex=s[s[first[V[j]]].nex].y;
int tmp=gas(nex,V[j])+1;
if(tmp>las) {ans=0;break;}
if(tmp<las) ad(ans,ans);
las=1;
}
else las++;
}
fac[0]=1;for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
inv[n]=ksm(fac[n],mod-2);for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++) if(tot[i]){
int tmp=0,op=1,po=1;
for(int k=0;2*k<=tot[i];k++)
tmp=(tmp+1ll*fac[tot[i]]*op%mod*inv[tot[i]-2*k]%mod*inv[k]%mod*po%mod*(((i&1)&&(i>1))?ksm(2,tot[i]-2*k):1))%mod,op=1ll*op*inv[2]%mod,po=1ll*po*i%mod;
ans=1ll*ans*tmp%mod;
}
printf("%d\n",ans);
}
F - Black Radius
这题不是一般的难。
考虑先设一个状态
f
(
x
,
d
)
f(x,d)
f(x,d)表示选择
x
x
x点,
d
d
d距离所对应的点集,我们先不计算
f
(
x
,
d
)
=
f(x,d)=
f(x,d)=全集的情况,最后
+
1
+1
+1就可以了。
然后考虑怎么去重。对于 f ( x , d 1 ) = f ( y , d 2 ) f(x,d_1)=f(y,d_2) f(x,d1)=f(y,d2),对于该路径的一系列点 v 0 = x , v 1 , v 2 , . . . , v m = y v_0=x,v_1,v_2,...,v_{m}=y v0=x,v1,v2,...,vm=y
该式子成立:
f
(
x
,
d
1
)
=
f
(
v
1
,
d
1
−
1
)
=
.
.
.
=
f
(
v
k
,
d
1
−
k
)
=
.
.
.
=
f
(
z
,
d
3
)
=
.
.
.
=
f
(
v
k
,
d
2
−
m
+
k
)
=
.
.
.
=
f
(
v
m
−
1
,
d
2
−
1
)
=
f
(
y
,
d
2
)
f(x,d_1)=f(v_1,d_1-1)=...=f(v_k,d_1-k)=...=f(z,d_3)=...=f(v_k,d_2-m+k)=...=f(v_{m-1},d_2-1)=f(y,d_2)
f(x,d1)=f(v1,d1−1)=...=f(vk,d1−k)=...=f(z,d3)=...=f(vk,d2−m+k)=...=f(vm−1,d2−1)=f(y,d2)
将
x
,
y
x,y
x,y拉到一条横线上,横线上面的点就是路径的点,将其他点摆在对应连接点的下放,我们管一个点下面的点叫做其的管辖点。
1.表示该路径上相等的点集所对应的距离
d
d
d一定会从
d
1
d_1
d1缩减到
d
3
d_3
d3,再从
d
3
d_3
d3增加到
d
2
d_2
d2。
2.而且以
z
z
z为根时,只有
z
z
z的某些管辖点没有被覆盖。
证明如下:
先证明2的话,1就显而易见了。
那么现在 z z z处于 x , y x,y x,y之间,如果除了 z z z外的点的管辖点有未被覆盖的,从 x x x到 z z z, y y y到 z z z考虑所有的 f ( v , d ) f(v,d) f(v,d),经过该点之后, d d d仍然不断减小,使得覆盖管辖点的数量不断减小,此时点集发生了改变,证毕。
靠近 z z z的时候显然 d d d要恰好减少 1 1 1,否则就会覆盖到更多的管辖点,使点集发生改变。
f ( v , d ) = f ( z , d 3 ) f(v,d)=f(z,d_3) f(v,d)=f(z,d3)的 d d d的最小值不一定是 d 3 d_3 d3,也就是说 z z z仍然可以走一条 d d d不断 − 1 -1 −1的路径走到最小值,而这个最小值唯一。
有了这个,我们就可以很轻易的判断 f ( x , d ) f(x,d) f(x,d)是否要被计入答案了,如果存在一个 x x x的相邻点 y y y,满足 f ( x , d ) = f ( y , d − 1 ) f(x,d)=f(y,d-1) f(x,d)=f(y,d−1),那么就不用管 f ( x , d ) f(x,d) f(x,d)。
简单来说一个点管理的距离其实是 [ 0 , min ( a − 1 , b + 1 ) ] [0,\min(a-1,b+1)] [0,min(a−1,b+1)],其中 a a a表示以该点为根的最大深度, b b b表示以该点为根的次大深度。
如何证明?首先前面一个限制是显然的,后面一个限制考虑什么情况下才会不变。显然是往最大深度那里移动一步,此时满足 d − 1 ≥ b + 1 d-1\geq b+1 d−1≥b+1,也就是说仍然可以包含次大子树,那么 d < b + 2 d<b+2 d<b+2的时候肯定就会发生改变了。
那么所有点都为好点的情况就会做了。
如果一个点不是好点,考虑怎么将该状态转移到另外一个好点?
我们只需要对于一个点来说,对于每一个子树看看是否有好点,如果有那么就对这个子树的最大深度取min,然后就得到了转移的下界。
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
struct edge{
int y,nex;
}s[N<<1];
int first[N],len=0,n,up[N],down[N],sz[N],g[N];
pair<int,int> f[N],tmp;
long long ans=0;
char ch[N];
void ins(int x,int y){
s[++len]=(edge){y,first[x]};first[x]=len;
}
void upd(pair<int,int>&x,int d){
if(d>x.first) x.second=x.first,x.first=d;
else if(d>x.second) x.second=d;
}
void dfs(int x,int fa){
f[x].first=0;f[x].second=0;
if(ch[x]=='1') sz[x]=1;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa)
dfs(s[i].y,x),upd(f[x],f[s[i].y].first+1),sz[x]+=sz[s[i].y];
}
void dfs_2(int x,int fa){
tmp.first=tmp.second=0;
upd(tmp,f[x].first);upd(tmp,f[x].second);upd(tmp,g[x]);
up[x]=min(tmp.first-1,tmp.second+1);
vector<int> pre,las;
if(ch[x]=='1') down[x]=0;
else down[x]=up[x]+1;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
pre.push_back(f[s[i].y].first+2);
las.push_back(f[s[i].y].first+2);
}
for(int i=1;i<pre.size();i++) pre[i]=max(pre[i],pre[i-1]);
for(int i=las.size()-2;i>=0;i--) las[i]=max(las[i],las[i+1]);
int t=0;
for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
int y=s[i].y;
g[y]=g[x]+1;
if(t) g[y]=max(g[y],pre[t-1]);
if(t!=las.size()-1) g[y]=max(g[y],las[t+1]);
t++;dfs_2(y,x);
if(sz[s[i].y]) down[x]=min(down[x],f[y].first+1);
}
if(sz[1]-sz[x]) down[x]=min(down[x],g[x]);
ans+=up[x]-down[x]+1;
}
int main(){
scanf("%d",&n);
int x,y;
for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
scanf("%s",ch+1);
dfs(1,0);dfs_2(1,0);
printf("%lld\n",ans+1);
}