这道题比较送分
考虑格雷码的生成方式
实际上每次如果下一位是1就和下一位是0的反转一下
考虑再下一位,如果还是1就会再反转一下,如果两位是0或两位是1就会变回原状,否则就会刚好反转,所以只会和前后两位有关,推一下就可以知道格雷码其实是
x
⊕
(
x
>
>
1
)
x \oplus (x>>1)
x⊕(x>>1)
考场上没推到
x
⊕
(
x
>
>
1
)
x \oplus (x>>1)
x⊕(x>>1),但是也可以写,影响不大。
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
int n,rev,ans[100];
ull k;
int main(){
scanf("%d%llu",&n,&k);
while(n--){
if(k<(1ull<<n)){
printf("%d",rev);
rev=0;
}
else{
printf("%d",rev^1);
rev=1;
k-=(1ull<<n);
}
}
return 0;
}
Day1T2 括号树
对于每一个点,考虑以这个点为末尾增加了多少个合法括号串,如果是左括号就是0,如果是右括号可以用栈找到当前点匹配的括号作为一个,再加上匹配点的父亲产生的合法括号串的个数,最后每个点再算链上的前缀和。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=500010;
typedef long long ll;
int n,tp,tot,f[N],head[N],to[N],nxt[N],stk[N],val[N];
ll dp[N],ans;
void add_edge(int u,int v){
nxt[++tot]=head[u];
to[tot]=v;
head[u]=tot;
return;
}
void dfs(int u){
int x=0;
if(tp>0&&val[stk[tp]]==0&&val[u]==1){
x=stk[tp--];
dp[u]+=dp[f[x]]+1;
}
else stk[++tp]=u;
for(int i=head[u];~i;i=nxt[i])
dfs(to[i]);
if(x)
stk[++tp]=x;
else tp--;
return;
}
void dfs2(int u){
ans^=dp[u]*u;
for(int i=head[u];~i;i=nxt[i]){
dp[to[i]]+=dp[u];
dfs2(to[i]);
}
return;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++){
char c;
do c=getchar();
while(c!='('&&c!=')');
if(c=='(')
val[i]=0;
else val[i]=1;
}
for(int i=2;i<=n;i++){
scanf("%d",f+i);
add_edge(f[i],i);
}
dfs(1);
dfs2(1);
printf("%lld\n",ans);
return 0;
}
Day1T3 树上的数
考虑贪心,对于尽量小的数应该移到尽量小的点上,会发现这样就可以产生一个边的次序,而且边的次序只对于一个点的所有连边有这样的顺序。如果一个数要从s点换到t点,次序大概是这样的:
对于s点,这条路经上的那条边一定是最先删除的
对于t点,这条路径上的那条边一定是最后删除的
对于路径上的点,这条路径上的那两条边一定是按顺序紧接着删除的
于是我们可以用一个链表维护每个点所连边之间的顺序,从小到大枚举每个数,dfs找到可以到达的最小点
具体怎么维护每个点连边的顺序,我们可以先对所有所连边建一个链表,在添加一个次序的时候合并两个链表。为了方便维护,因为在链表中间的边显然不会再连边,所以可以将nxt和pre直接设为-1,如果这条边是第一条删除的边,可以把pre设为0,最后一条边同理。
连边和判断能否走到下一个点具体的可以看代码实现,有点难讲。
代码:
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
int T,n,tot,mini,fa[N],a[N],deg[N],head[N],to[N*N],nxt[N*N],pre[N][N],nt[N][N];
void add_edge(int u,int v){
nxt[++tot]=head[u];
to[tot]=v;
head[u]=tot;
return;
}
bool check(int u,int l,int r){
if(pre[u][l]==-1||nt[u][r]==-1||nt[u][r]==l||(pre[u][l]==0&&nt[u][r]==n+1&°[u]!=2))
return 0;
return 1;
}
void dfs(int u){
if(fa[u]&&check(u,fa[u],n+1))
mini=min(mini,u);
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(v==fa[u]||!check(u,fa[u],v))
continue;
fa[v]=u;
dfs(v);
}
return;
}
void merge(int u,int l,int r){
nt[u][pre[u][l]]=nt[u][r];
pre[u][nt[u][r]]=pre[u][l];
nt[u][r]=pre[u][l]=-1;
deg[u]--;
return;
}
void solve(int x){
merge(x,fa[x],n+1);
while(fa[fa[x]]){
int tmp=x;
x=fa[x];
merge(x,fa[x],tmp);
}
int tmp=x;
x=fa[x];
merge(x,0,tmp);
return;
}
int main(){
scanf("%d",&T);
while(T--){
tot=0;
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
deg[i]=2;
}
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
deg[u]++;
deg[v]++;
}
for(int i=1;i<=n;i++)
for(int j=0;j<=n+1;j++)
pre[i][j]=nt[i][j]=j;
for(int i=1;i<=n;i++){
mini=0x7f7f7f7f;
fa[a[i]]=0;
dfs(a[i]);
solve(mini);
printf("%d ",mini);
}
putchar('\n');
}
return 0;
}
Day2T1 Emiya 家今天的饭
首先显然做的菜的数量小于等于会的烹饪方法数
考虑到直接求比较复杂,先不考虑每种食材的出现次数,求出总方案数,显然可以一个dp或者数学推一下就出来了。
这里给出dp的方法:
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示前i个烹饪方法中做了j道菜的方案数,则有:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
1
]
∗
s
u
m
[
i
]
dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*sum[i]
dp[i][j]=dp[i−1][j]+dp[i−1][j−1]∗sum[i]
其中
s
u
m
[
i
]
sum[i]
sum[i]表示第i种烹饪方式中可以做的菜的总数。
再考虑题目的限制,显然最多只有一种主要食材在超过一半的菜中出现,所以我们可以直接枚举主要食材,然后求方案数之后从总方案数减去就ok了。
怎么在已知一个在超过一半的菜的主要食材时求出方案数呢?
考虑一个朴素的dp,
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示前i种烹饪方法中做了j道菜,其中有k道菜是用已知食材做的。
那么就可以得出:
f
[
i
]
[
j
]
[
k
]
=
(
f
[
i
−
1
]
[
j
]
[
k
]
+
f
[
i
−
1
]
[
j
−
1
]
[
k
]
∗
(
s
u
m
[
i
]
−
a
[
i
]
[
n
o
w
]
)
+
f
[
i
−
1
]
[
j
]
[
k
−
1
]
∗
a
[
i
]
[
n
o
w
]
)
f[i][j][k]=(f[i-1][j][k]+f[i-1][j-1][k]*(sum[i]-a[i][now])+f[i-1][j][k-1]*a[i][now])
f[i][j][k]=(f[i−1][j][k]+f[i−1][j−1][k]∗(sum[i]−a[i][now])+f[i−1][j][k−1]∗a[i][now])
其中now表示当前已知食材。
于是就有了
O
(
m
n
3
)
O(mn^3)
O(mn3)的84分优秀做法。
注意到最终的状态只有极小部分的是需要用到了,那么是不是可以考虑一下压缩状态呢?
我们会发现用当前已知食材的菜超过一半,会发现这样就会比其它所有菜的数量加起来要多。
所以我们可以把dp的后两维压成一维,表示当前已知食材做的菜的数量和其它食材做的菜的差,于是就有了dp方程:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
f
[
i
−
1
]
[
j
−
1
]
∗
a
[
i
]
[
n
o
w
]
+
f
[
i
−
1
]
[
j
+
1
]
∗
(
s
u
m
[
i
]
−
a
[
i
]
[
n
o
w
]
)
f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][now]+f[i-1][j+1]*(sum[i]-a[i][now])
f[i][j]=f[i−1][j]+f[i−1][j−1]∗a[i][now]+f[i−1][j+1]∗(sum[i]−a[i][now])
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=110,M=2010;
const int mod=998244353;
int n,m,ans,a[N][M],sum[N],dp[N][N],g[N][N*2],*f[N];
int Add(int a,int b){
return a+b>=mod?a+b-mod:a+b;
}
int Minus(int a,int b){
return a<b?a-b+mod:a-b;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",a[i]+j);
sum[i]=Add(sum[i],a[i][j]);
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
dp[i][0]=dp[i-1][0];
for(int j=1;j<=n;j++)
dp[i][j]=(dp[i-1][j]+1ll*dp[i-1][j-1]*sum[i])%mod;
}
for(int i=1;i<=n;i++)
ans=Add(ans,dp[n][i]);
for(int i=0;i<=n;i++)
f[i]=g[i]+N;
for(int now=1;now<=m;now++){
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=-n;j<=n;j++)
f[i][j]=Add(f[i-1][j],Add(1ll*f[i-1][j-1]*a[i][now]%mod,1ll*f[i-1][j+1]*Minus(sum[i],a[i][now])%mod));
for(int i=1;i<=n;i++)
ans=Minus(ans,f[n][i]);
}
printf("%d",ans);
return 0;
}
Day2T2
这道题有个的结论:最优解的最后一段会尽量小
证明的话可以用数学归纳法:
设
d
p
[
m
]
dp[m]
dp[m]表示前m项的答案,
s
u
m
[
m
]
sum[m]
sum[m]表示前m项的和,最后一段最小时左端点为
j
j
j,最后一段存在的左端点为
k
k
k
所以问题转化为证明不等式:
d
p
[
i
]
=
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
≤
d
p
[
k
]
+
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
2
dp[i]=dp[j]+(sum[i]-sum[j])^2\le dp[k]+(sum[i]-sum[k])^2
dp[i]=dp[j]+(sum[i]−sum[j])2≤dp[k]+(sum[i]−sum[k])2
i=0就不说了
记当前所求的是前i项的答案
假设
m
m
m在
m
m
m<
i
i
i时原命题成立,
考虑作差法证明不等式:
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
−
d
p
[
k
]
−
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
2
dp[j]+(sum[i]-sum[j])^2-dp[k]-(sum[i]-sum[k])^2
dp[j]+(sum[i]−sum[j])2−dp[k]−(sum[i]−sum[k])2
=
d
p
[
j
]
−
d
p
[
k
]
+
(
2
s
u
m
[
i
]
−
s
u
m
[
k
]
−
s
u
m
[
j
]
)
(
s
u
m
[
k
]
−
s
u
m
[
j
]
)
=dp[j]-dp[k]+(2sum[i]-sum[k]-sum[j])(sum[k]-sum[j])
=dp[j]−dp[k]+(2sum[i]−sum[k]−sum[j])(sum[k]−sum[j])
≤
d
p
[
j
]
−
d
p
[
k
]
−
(
s
u
m
[
j
]
−
s
u
m
[
k
]
)
2
\le dp[j]-dp[k]-(sum[j]-sum[k])^2
≤dp[j]−dp[k]−(sum[j]−sum[k])2
∵
d
p
[
j
]
≤
d
p
[
k
]
+
(
s
u
m
[
j
]
−
s
u
m
[
k
]
)
2
\because dp[j]\le dp[k]+(sum[j]-sum[k])^2
∵dp[j]≤dp[k]+(sum[j]−sum[k])2
∴
d
p
[
j
]
−
d
p
[
k
]
−
(
s
u
m
[
j
]
−
s
u
m
[
k
]
)
2
≤
0
\therefore dp[j]-dp[k]-(sum[j]-sum[k])^2\le0
∴dp[j]−dp[k]−(sum[j]−sum[k])2≤0
∴
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
−
d
p
[
k
]
−
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
2
≤
0
\therefore dp[j]+(sum[i]-sum[j])^2-dp[k]-(sum[i]-sum[k])^2\le0
∴dp[j]+(sum[i]−sum[j])2−dp[k]−(sum[i]−sum[k])2≤0
原命题得证。
有了这个结论就可以直接一个单调队列求出最后一段的左端点,然后直接算答案就好了。
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=40000010,base=1e9;
typedef long long ll;
int n,type,hd,tl,q[N];
ll a[N],f[N];
struct data{
int a[4];
data(){
a[0]=a[1]=a[2]=a[3]=0;
}
int& operator[](int x){
return a[x];
}
friend data operator+(data x,data y){
ll ret[4];
for(int i=0;i<4;i++)
ret[i]=x[i]+y[i];
for(int i=0;i<3;i++){
ret[i+1]+=ret[i]/base;
ret[i]%=base;
}
data ans;
for(int i=0;i<4;i++)
ans[i]=ret[i];
return ans;
}
void write(){
bool ck=0;
for(int i=3;i>=0;i--)
if(a[i]&&!ck){
ck=1;
printf("%d",a[i]);
}
else if(ck)
printf("%09d",a[i]);
return;
}
};
data Sqr(ll x){
ll tmp[4]={0,0,0,0};
tmp[0]=x%base;
tmp[1]=x/base;
tmp[2]=tmp[1]*tmp[1];
tmp[1]=tmp[0]*tmp[1]*2;
tmp[0]=tmp[0]*tmp[0];
for(int i=0;i<3;i++){
tmp[i+1]+=tmp[i]/base;
tmp[i]%=base;
}
data ans;
for(int i=0;i<4;i++)
ans[i]=tmp[i];
return ans;
}
void rd0(){
for(int i=1;i<=n;i++)
scanf("%d",a+i);
return;
}
void rd1(){
int x,y,z,m,p,l,r,lstp=0;
scanf("%d%d%d%d%d%d",&x,&y,&z,a+1,a+2,&m);
for(int i=3;i<=n;i++)
a[i]=(1ll*x*a[i-1]+1ll*y*a[i-2]+z)&((1<<30)-1);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&p,&l,&r);
p=min(p,n);
for(int j=lstp+1;j<=p;j++)
a[j]=a[j]%(r-l+1)+l;
lstp=p;
}
return;
}
int main(){
scanf("%d%d",&n,&type);
if(type)
rd1();
else
rd0();
for(int i=1;i<=n;i++){
a[i]+=a[i-1];
while(hd<tl&&a[q[hd+1]]-a[f[q[hd+1]]]<=a[i]-a[q[hd+1]])
hd++;
f[i]=q[hd];
while(hd<tl&&a[q[tl]]-a[f[q[tl]]]+a[q[tl]]>a[i]-a[f[i]]+a[i])
tl--;
q[++tl]=i;
}
data ans;
for(int x=n;x;x=f[x])
ans=ans+Sqr(a[x]-a[f[x]]);
ans.write();
return 0;
}
Day2T3 树的重心
直接在dfs的时候一边换为以x为根,然后会发现一棵树的重心会在所有重链的交集上,维护一个倍增跳重儿子找重心,如果有另一个重心一定是父亲或者重儿子,直接判就行。
怎么维护重儿子可以见代码。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300010;
typedef long long ll;
int T,n,tot,head[N],to[N*2],nxt[N*2],f[N],siz[N],son[N][20],secson[N];
ll ans;
void add_edge(int u,int v){
nxt[++tot]=head[u];
to[tot]=v;
head[u]=tot;
return;
}
void dfs1(int u){
siz[u]=1;
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(v==f[u])
continue;
f[v]=u;
dfs1(v);
if(siz[v]>siz[son[u][0]]){
secson[u]=son[u][0];
son[u][0]=v;
}
else if(siz[v]>siz[secson[u]])
secson[u]=v;
siz[u]+=siz[v];
}
for(int i=1;i<=18;i++)
son[u][i]=son[son[u][i-1]][i-1];
return;
}
void dfs2(int u,int father){
int s=son[u][0],sc=secson[u],sz=siz[u];
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(v==father)
continue;
int x=v;
for(int i=17;i>=0;i--)
if(siz[v]-siz[son[x][i]]<=siz[v]/2)
x=son[x][i];
if(max(siz[v]-siz[x],siz[son[x][0]])<=siz[v]/2)
ans+=x;
if(max(siz[v]-siz[son[x][0]],siz[son[son[x][0]][0]])<=siz[v]/2)
ans+=son[x][0];
if(max(siz[v]-siz[f[x]],siz[son[f[x]][0]])<=siz[v]/2)
ans+=f[x];
siz[u]=n-siz[v];
son[u][0]=father;
f[u]=0;
if(s!=v)
if(siz[son[u][0]]<siz[s])
son[u][0]=s;
if(sc!=v)
if(siz[son[u][0]]<siz[sc])
son[u][0]=sc;
for(int i=1;i<=17;i++)
son[u][i]=son[son[u][i-1]][i-1];
x=u;
for(int i=17;i>=0;i--)
if(siz[u]-siz[son[x][i]]<=siz[u]/2)
x=son[x][i];
if(max(siz[u]-siz[x],siz[son[x][0]])<=siz[u]/2)
ans+=x;
if(max(siz[u]-siz[son[x][0]],siz[son[son[x][0]][0]])<=siz[u]/2)
ans+=son[x][0];
if(max(siz[u]-siz[f[x]],siz[son[f[x]][0]])<=siz[u]/2)
ans+=f[x];
f[u]=v;
dfs2(v,u);
}
f[u]=father;
siz[u]=sz;
secson[u]=sc;
son[u][0]=s;
for(int i=1;i<=17;i++)
son[u][i]=son[son[u][i-1]][i-1];
return;
}
int main(){
scanf("%d",&T);
while(T--){
tot=ans=0;
memset(head,-1,sizeof(head));
memset(son,0,sizeof(son));
memset(secson,0,sizeof(secson));
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs1(1);
dfs2(1,0);
printf("%lld\n",ans);
}
return 0;
}