A - Simple Calculator
分情况讨论一下,要仔细一点。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int x,y;
int main()
{
scanf("%d%d",&x,&y);
if(x==y) puts("0");
else if(y>x) printf("%d\n",min(y-x,abs(y+x)+1));
else printf("%d\n",min(abs(x+y)+1,2+abs(x-y)));
return 0;
}
B - Contiguous Repainting
除了最后一次染色的那个长度为 k k k的区间以外的所有格子,都可以任意确定颜色(方法就是先贴着整个序列两侧用区间涂色,然后慢慢地把涂色区间向中间挪,这样就可以一格一格地确定颜色)。
那么只要用前缀和处理一下,枚举这个最后涂色的区间即可。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0,w=1;char ch=' ';
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q*w;
}
const int N=100005;
typedef long long LL;
int n,K;LL a[N],s1[N],s2[N],ans;
int main()
{
n=read(),K=read();
for(RI i=1;i<=n;++i) {
LL x=read();
s1[i]=s1[i-1]+max(0LL,x),s2[i]=s2[i-1]+x;
}
for(RI i=K;i<=n;++i) {
ans=max(ans,s2[i]-s2[i-K]+s1[i-K]+s1[n]-s1[i]);
ans=max(ans,s1[i-K]+s1[n]-s1[i]);
}
printf("%lld\n",ans);
return 0;
}
C - Tetromino Tiling
"o"型的方块直接放就行了,剩下的只有:
- 两个"L"或者两个"J"或两个"I"组成 2 ∗ 4 2*4 2∗4的方块
- 一个"L"一个"J"一个"I"组成 2 ∗ 6 2*6 2∗6的方块
分类讨论即可。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
LL ans,a,b,c,d,e,f,g;
int main()
{
scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&e,&f,&g);
ans+=b;
if(a&&d&&e&&((a&1)+(d&1)+(e&1)>=2)) --a,--d,--e,ans+=3;
ans+=2*(a/2+d/2+e/2);
printf("%lld\n",ans);
return 0;
}
D - K-th K
这个…你就按照被钦定的位置从小到大排序,假设数对 ( x , k ) (x,k) (x,k)表示在 a x a_x ax是第 k k k个 k k k,那么就钦定 a x = k a_x=k ax=k,然后找到最前面的 k − 1 k-1 k−1个空位填上 k k k。这么搞完一轮后,再按 x x x从小到大,把剩下的 k k k往剩下的空位里填,同时判断合不合法即可。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int a[250005],x[505],n;
struct node{int v,x;}t[505];
bool cmp(node k1,node k2) {return k1.x<k2.x;}
int main()
{
int x;scanf("%d",&n);
for(RI i=1;i<=n;++i) scanf("%d",&x),a[x]=i,t[i]=(node){i,x};
sort(t+1,t+1+n,cmp);
int j=1;
for(RI i=1;i<=n;++i) {
for(RI k=1;k<=t[i].v-1;++k) {
while(j<n*n&&a[j]) ++j;
if(a[j]||j>t[i].x) {puts("No");return 0;}
a[j]=t[i].v;
}
}
for(RI i=1;i<=n;++i) {
for(RI k=t[i].v+1;k<=n;++k) {
while(j<n*n&&a[j]) ++j;
if(a[j]||j<t[i].x) {puts("No");return 0;}
a[j]=t[i].v;
}
}
puts("Yes");
for(RI i=1;i<=n*n;++i) printf("%d ",a[i]);
return 0;
}
E - Next or Nextnext
水完了前面四道水题,就获得了一道神题。
litble不生产题解,litble只是官方题解的翻译工,以下图都是从官方题解搬过来的。
我们考虑最终的那个排列 p p p,建一个图,将点 i i i向点 p i p_i pi连边。因为这是个排列,所以每个点出度入度都为 1 1 1,所以一定由若干环构成。
考虑其中的一个环,我们擦掉它的所有边,然后将 i i i向 a i a_i ai连边,可以知道 a i a_i ai是它前面一个节点或者前面的前面一个节点。
有四种情况。
- 所有 i i i的 a i a_i ai都是它的前面一个节点,则环保持不变。
- 所有 i i i的 a i a_i ai都是它前面的前面的节点,且环为奇环,则环变成同构的另一个环
- 所有 i i i的 a i a_i ai都是它前面的前面的点,且原环为偶环,则这个环会被拆成两个相同大小的环。
- 有的是前面一个节点,有的又是前面的前面,则变成了一棵由一个环和若干指向环的链构成的基环内向树。
行吧,现在我们手头上只有由 a a a构成的那张图,没有由 p p p构成的那张图,所以我们就要反过来考虑了。
首先我们找到所有的环,先记录每个大小的环有多少个,那么每种大小可以单独考虑,DP一下,决策就是这种大小的第 k k k个环是和前面的环合并呢,还是单独组成 p p p图中的环。最后用乘法原理乘起来这些东西。
对于一棵基环内向树,我们考虑相邻两个“脚”(即挂在环上的链),将“脚”往环里面塞,并且要求还是一条边与它指着的节点中间最多只能插一个节点。大致如下图:
这个脚可以塞到树里的位置,就是到下一个脚之间的边,假设这些边有 l 2 l_2 l2条,这个脚的长度为 l 1 l_1 l1,那么:
若 l 2 < l 1 l_2<l_1 l2<l1,有0种方案。
若 l 2 = l 1 l_2=l_1 l2=l1,有1种方案。
若 l 2 > l 1 l_2>l_1 l2>l1,有2种方案。
也可以用乘法原理搞,就做完了。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int mod=1e9+7,N=100005;
int n,ans;
int a[N],du[N],cir[N],vis[N],footL[N],sum[N],f[N];
int qm(int x) {return x>=mod?x-mod:x;}
void workcir(int x) {
int now=0,fr=0,ed=0,frL=0;
//fr:第一个有脚的位置,ed:上一个找到的有脚的位置
//frL:第一个脚的长度,now:当前节点是从x开始走环走到的第几个点
while(cir[x]) {
++now,cir[x]=0;
if(footL[x]) {
if(!fr) ed=fr=now,frL=footL[x];
else {//塞脚
int kl=(footL[x]<now-ed)+(footL[x]<=now-ed);
ans=1LL*ans*kl%mod,ed=now;
}
}
x=a[x];
}
if(!fr) ++sum[now];//是简单环
else {//考虑第一个脚
int kl=(frL<now-ed+fr)+(frL<=now-ed+fr);
ans=1LL*ans*kl%mod;
}
}
void work() {
for(RI i=1;i<=n;++i) {
if(du[i]) continue;
int x=i,len=0;while(!cir[x]) x=a[x],++len;
footL[x]=len;//算挂在每个点上的脚长
}
ans=1;
for(RI i=1;i<=n;++i) if(cir[i]) workcir(i);
for(RI i=1;i<=n;++i) {//对每一种长度的简单环做DP
if(!sum[i]) continue;
f[0]=1;
for(RI j=1;j<=sum[i];++j) {
if(i>1&&(i&1)) f[j]=qm(f[j-1]+f[j-1]);//情况1,2
else f[j]=f[j-1];//情况1
if(j>1) f[j]=qm(f[j]+1LL*f[j-2]*(j-1)%mod*i%mod);//情况3
}
ans=1LL*ans*f[sum[i]]%mod;
}
}
int main()
{
n=read();
for(RI i=1;i<=n;++i) a[i]=read(),++du[a[i]];
for(RI i=1;i<=n;++i) {
if(vis[i]) continue;
int x=i;while(!vis[x]) vis[x]=i,x=a[x];
if(vis[x]!=i) continue;//说明i在一个脚上
while(!cir[x]) cir[x]=1,x=a[x];//给环打上是环标记
}
for(RI i=1;i<=n;++i)//判无解
if((cir[i]&&du[i]>2)||(!cir[i]&&du[i]>1)) {puts("0");return 0;}
work();
printf("%d\n",ans);
return 0;
}
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)同构,我们希望只在 d d d最小的那个位置计算贡献,则我们计算满足以下条件的数对个数:
- f ( x , d ) f(x,d) f(x,d)不覆盖整棵树(计数完成后让答案+1即可统计整棵树被染色的情况)
- 对于与 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)对于与 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)
所以我们发现,每个点上可以取的 d d d存在一个上界。
设离 x x x最远的点离 x x x的距离为 m x ( x ) mx(x) mx(x),显然条件1等价于 d < m x ( x ) d<mx(x) d<mx(x)。
而条件2,考虑若存在一个这样的 y y y,把 x x x看做树根。由于 y y y能够染周围 d − 1 d-1 d−1的点,所以在 y y y子树里的点,若存在于 f ( x , d ) f(x,d) f(x,d)中必然存在在 f ( y , d − 1 ) f(y,d-1) f(y,d−1)中,反之不存在于。而若在 y y y子树以外的地方有一个点 z z z和 x x x的距离大于 d − 2 d-2 d−2,则 f ( y , d − 1 ) f(y,d-1) f(y,d−1)中没有它,而 f ( x , d ) f(x,d) f(x,d)中有它。
因此若设 s e ( x ) se(x) se(x)表示删掉 y y y子树后剩余节点中找一个点,使得 x x x离它的距离最远,则条件2满足等价于 d − 2 < s e ( x ) d-2<se(x) d−2<se(x)。显然在 y y y子树里存在到 x x x距离最远的点时, s e ( x ) se(x) se(x)最小,最能产生约束。
现在看有的点不是关键点的情况。
那么对于不是关键点的点 x x x,若要 f ( x , d ) f(x,d) f(x,d)是与它相同的集合表示中, d d d最小的那个,且也是一个关键点 y y y的某种染色集合,则这个集合必须包含以 x x x为根,它的儿子们的子树中,包含 y y y的那个子树中的所有节点。(有点拗口啊QAQ)
证明:
如果不包含的话,如下图,
k
k
k以下的部分没包含,则
f
(
x
,
d
2
+
d
3
)
=
f
(
y
,
d
1
+
d
3
)
f(x,d_2+d_3)=f(y,d_1+d_3)
f(x,d2+d3)=f(y,d1+d3),从
y
y
y走到
x
x
x的距离是
d
1
+
d
2
d_1+d_2
d1+d2,走到
x
x
x再走到
x
x
x的其他儿子,还能走的距离是
d
3
−
d
2
d_3-d_2
d3−d2。而对于状态
f
(
z
,
d
3
)
f(z,d_3)
f(z,d3),从
z
z
z走到
x
x
x后再能走的距离也是
d
3
−
d
2
d_3-d_2
d3−d2,而且也能走到
k
k
k,所以
f
(
z
,
d
3
)
=
f
(
x
,
d
2
+
d
3
)
f(z,d_3)=f(x,d_2+d_3)
f(z,d3)=f(x,d2+d3),
f
(
x
,
d
2
+
d
3
)
f(x,d_2+d_3)
f(x,d2+d3)不是与它相同的集合表示中
d
d
d最小的那个。
于是我们就做换根DP即可。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
typedef long long LL;
const int N=200005,inf=0x3f3f3f3f;
int h[N],ne[N<<1],to[N<<1],mx[N],se[N],d[N],sz[N];
int n,tot;LL ans;char S[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs1(int x,int las) {
if(S[x]=='1') d[x]=0,sz[x]=1;
else d[x]=inf;
for(RI i=h[x];i;i=ne[i]) {
if(to[i]==las) continue;
int y=to[i];dfs1(y,x),sz[x]+=sz[y];
if(mx[y]+1>mx[x]) se[x]=mx[x],mx[x]=mx[y]+1;
else if(mx[y]+1>se[x]) se[x]=mx[y]+1;
if(sz[y]) d[x]=min(d[x],mx[y]+1);
}
}
void dfs2(int x,int las) {
int R=min(se[x]+1,mx[x]-1);
if(d[x]<=R) ans+=(LL)(R-d[x]+1);
for(RI i=h[x];i;i=ne[i]) {
if(to[i]==las) continue;
int y=to[i],kl=(mx[y]+1==mx[x]?se[x]+1:mx[x]+1);
if(kl>mx[y]) se[y]=mx[y],mx[y]=kl;
else if(kl>se[y]) se[y]=kl;
if(sz[1]-sz[y]) d[y]=min(d[y],kl);
dfs2(y,x);
}
}
int main()
{
int x,y;
n=read();
for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
scanf("%s",S+1);
dfs1(1,0),dfs2(1,0);
printf("%lld\n",ans+1);
return 0;
}