T1 格雷码
认证前几天某odgd还说不考格雷码
然后就考了
考虑从高到低处理每一位,设当前处理到了第(i)位,待处理数字是(j)的状态为(f(i,j))。
-
若(j<2^{i-1}),则输出
0
,处理(f(i-1,j))。 -
若(jgeqslant 2^{i-1}),则输出
1
,将(j)的后(i)位取反,处理(f(i-1,j))。
另外,要注意1ull<<64
爆unsigned long long
的问题。
code:
#include<stdio.h>
int n;
char c=0;
unsigned long long val=0;
void dfs(int p,unsigned long long q){
if(q<1ull<<p-1){
printf("0");
}else{
printf("1");
q-=1ull<<p-1;
q=(1ull<<p-1)-q-1;
}if(p-1>0)dfs(p-1,q);
}
int main(){
scanf("%d",&n);
while(c<'!')c=getchar();
while(c>='!')val=val*10+(c-'0'),c=getchar();
dfs(n,val);
}
T2 括号树
一道树上dp的好题不会栈的做法啊啊啊
设(t[i],s[i]):
-
若第(i)个括号为()),且在根结点到(i)号结点的简单路径上,存在节点(j),使得从(j)到(i)按结点经过顺序依次排列组成的字符串为合法括号串,则(t[i])为满足条件的(j)中深度最小的点,(s[i])为满足条件的(j)的数量。
-
若第(i)个括号为()),且在根结点到(i)号结点的简单路径上,不存在节点(j),使得从(j)到(i)按结点经过顺序依次排列组成的字符串为合法括号串,则(t[i]=s[i]=0)。
-
若第(i)个括号为((),则(t[i]=i,s[i]=0)。
转移:设当前讨论到了(p)号节点,需要讨论它的所有儿子
更新信息:若第(i)个括号为()),则(t[p])需要一直往上跳到(q),使得(q)为满足从(q)到(p)按结点经过顺序依次排列组成的字符串为合法括号串的(q)中深度最小的点,否则(t[p]=p)。
- 当儿子为((),则令(t[son]=son,s[son]=0)。
- 当儿子为()):
- 当该节点为((),则(t[son]=f[p]),(s[son]=s[f[p]]+1)
- 当该节点为()):
- 当(t[p]==1)或(t[p]==0),说明没有与儿子配对的((),则(t[son]=s[son]=0)。
- 否则说明有与儿子配对的((),则(t[son]=f[p]),(s[son]=s[f[f[p]]]+1)。
code:
#include<cstdio>
int Last[500002],Next[500002],val[500002],f[500002];
int t[500002],g[500002],n,tp=0;
long long s[500002],ans=0;
char c;
void dfs(int p){
if(!val[p])t[p]=p;
else{
if(!s[p])t[p]=0;
else{
if(val[f[t[p]]])t[p]=t[f[t[p]]];
}
}
for(int i=Last[p];i;i=Next[i]){
if(val[i]){
if(!val[p])t[i]=p,s[i]=s[f[p]]+1;
else{
if(t[p]==0||t[p]==1)s[i]=0,t[i]=0;
else t[i]=f[t[p]],s[i]=s[f[t[i]]]+1;
}
}dfs(i);
}
}
void dfs1(int p){
ans^=s[p]*p;
for(int i=Last[p];i;i=Next[i]){
s[i]+=s[p];
dfs1(i);
}
}
int main(){
scanf("%d",&n);
c=getchar();
while(c<'!')c=getchar();
while(c>='!')val[++tp]=c==')',c=getchar();
for(int i=2;i<=n;i++){
scanf("%d",&f[i]);
Next[i]=Last[f[i]];Last[f[i]]=i;
}dfs(1);dfs1(1);printf("%lld",ans);
}
T3 树上的数
就是乱搞贪*害我投了150分钟进去。
思考一下每一个数字在树上移动的轨迹,设(i)移动轨迹为(e_0,p_{i,0},e_{i,1},p_{i,2},e_{i,2},dots,p_{i,n_i-1},e_{i,n_i-1},p_{i,n_i},e_0)(前后补上(e_0)),容易发现以下几条规律:
- 对于每个节点(u),它在这些轨迹中出现次数为其度数(+1)。
- 对于每一条边(e_i(u,v)),它在这些轨迹中出现次数为2,且一次为(u,e_i,v),一次为(v,e_i,u)。
- 对于每个节点(u),不存在任意一个数字集合(S),使(S)中每一个数字的移动轨迹中与(u)相邻的边都出现过两次,且不同边的数量不为(u)的度数(+1)。(感性理解)
然后就有一个很暴力的思路:对每一个点(u)维护一个并查集,记录哪些边在已讨论过的路径中出现过(e_i,u,e_j)的情况,然后贪心,从(1 ext{~}n)枚举每一个节点结束时可能的最小数字。
(O(Tn^2log n)),相信( ext{CCF})少爷机。
code:
#include<cstdio>
int Last[2002],Next[4002],End[4002],Len[4002],val[2002],mk[2002],rc[2002],T,n,f[2002],mp[2002][2002];
int ff[2002][2002],cntt[2002],ts[2002][2002],tb[2002][2002],ans[2002];
inline int gf(int p,int q){return ff[p][q]==q?q:(ff[p][q]=gf(p,ff[p][q]));}
void dfs(int p){
int F=f[p],r=gf(p,F),k=gf(p,0);
if(r!=k||ts[p][k]==cntt[p]||ts[p][k]>tb[p][k]+1)rc[p]=1;
for(int i=Last[p];i;i=Next[i])if(End[i]!=f[p]&&!Len[i]){
int s=gf(p,End[i]);f[End[i]]=p;
if(s!=r||ts[p][s]==cntt[p]||ts[p][s]>tb[p][s]+1)dfs(End[i]);
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
Last[i]=mk[i]=ans[i]=0;
scanf("%d",&val[i]);cntt[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=0;j<=n;j++){
ff[i][j]=j;
ts[i][j]=1;
tb[i][j]=0;
mp[i][j]=0;
}
}
for(int i=2;i<=n+n-2;i+=2){
scanf("%d%d",&End[i+1],&End[i]);Len[i]=Len[i+1]=0;
mp[End[i+1]][End[i]]=i;
mp[End[i]][End[i+1]]=i+1;
cntt[End[i]]++;cntt[End[i+1]]++;
Next[i]=Last[End[i+1]];Last[End[i+1]]=i;
Next[i+1]=Last[End[i]];Last[End[i]]=i+1;
}for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)rc[j]=0,f[j]=0;
dfs(val[i]);
for(int j=1;j<=n;j++){
if(!mk[j]&&rc[j]&&val[i]!=j){
ans[i]=j;mk[j]=1;
int p=j,tmp=0,tmp2=0;
while(1){
if(tmp){
int s=gf(tmp,p),t=gf(tmp,tmp2);
ff[tmp][s]=t;
ts[tmp][t]+=ts[tmp][s];
tb[tmp][t]+=tb[tmp][s]+1;
}
Len[mp[f[p]][p]]=1;tmp2=tmp;tmp=p;p=f[p];
if(!tmp)break;
}break;
}
}
}for(int i=1;i<=n;i++)printf("%d ",ans[i]);puts("");
}
}
T4 Emiya 家今天的饭
看到Yazid就想起这到题,还都是dp
首先,很容易就能想到用合法的方案数减去非法的方案数。
设(f[t][i][j][k])表示当前讨论到第(t)种物品,第(i)种方法,一共用了(j)种方法,其中有(k)种方法用的是物品(t)。
然后可以滚掉(t)这一维,得到(f[i][j][k]),(O(n^3m))。
还可以合并(j,k)两维,设(f[i][j])表示当前讨论到第(i)种方法,用的方法数(-)用的物品(t)数( imes 2=j)。
(f[i][j]=f[i-1][j]+f[i-1][j-1]*(sum[i]-a[i][t])+f[i-1][j+1]*a[i][t])
code:
#include<cstdio>
#include<memory.h>
#define inf 998244353
int a[102][2002],n,m,ans=1;
int dp[102][222],s[102];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
s[i]=0;
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
s[i]+=a[i][j];
if(s[i]>=inf)s[i]-=inf;
}ans=1ll*ans*(s[i]+1)%inf;
}ans+=inf-1;
if(ans>=inf)ans-=inf;
for(int t=1;t<=m;t++){
memset(dp,0,sizeof(dp));
dp[0][n+1]=1;//n+1+a-b*2
for(int i=1;i<=n;i++){
for(int j=n+1-i;j<=n+1+i;j++){
dp[i][j]=(1ll*dp[i-1][j-1]*(s[i]+inf-a[i][t])+1ll*dp[i-1][j+1]*a[i][t]+dp[i-1][j])%inf;
}
}for(int i=n;i>=1;i--){
ans+=inf-dp[n][i];
while(ans>=inf)ans-=inf;
}
}printf("%d ",ans);
}
T5 划分
盲猜结论题
先前缀和一遍,再设(t[i])为前(i)个数的最优划分中倒数第二段的最后一个位置。
维护一个单调栈,保证里面的元素(p)满足(s[p] imes 2-s[t[p]])单调不降,再在上面维护一个指针,指针只往右移。
每次找到栈里最大的满足(s[i]>=s[p] imes 2-s[t[p]])的数,即为(t[i])。(我也很迷)
再插入(i),总时间(O(n))。
吐槽高精慢死了
code:
#include<cstdio>
#include<deque>
#include<bits/stdc++.h>
__int128 ans=0,e=1;
int t[40000002],n,T;
long long s[40000002];
int S[40000002],g[40000002],tp=0,pos=0;
int main(){
scanf("%d%d",&n,&T);
if(!T)for(int i=1;i<=n;i++)scanf("%lld",&s[i]),s[i]+=s[i-1];
else{
long long x,y,z,m,lj=0;
scanf("%lld%lld%lld%lld%lld%lld",&x,&y,&z,&s[1],&s[2],&m);
for(int i=3;i<=n;i++)s[i]=(x*s[i-1]+y*s[i-2]+z)&1073741823ll;
while(m--){
scanf("%lld%lld%lld",&x,&y,&z);
for(int i=lj+1;i<=x;i++)s[i]=s[i]%(z-y+1)+y;
lj=x;
}for(int i=1;i<=n;i++)s[i]+=s[i-1];
}S[++tp]=0;
for(int i=1;i<=n;i++){
while(pos<tp&&s[i]-s[S[pos+1]]>=s[S[pos+1]]-s[t[S[pos+1]]])pos++;
t[i]=S[pos];g[i]=g[t[i]]+1;
while(tp&&s[S[tp]]+s[S[tp]]-s[t[S[tp]]]>=s[i]+s[i]-s[t[i]])tp--;
S[++tp]=i;
}for(int i=n;i;i=t[i]){
ans+=e*(s[i]-s[t[i]])*(s[i]-s[t[i]]);
}if(ans<1e18){
long long k=ans;
printf("%lld",k);
}else{
long long k1=ans/1000000000000000000,k2=ans%1000000000000000000;
printf("%lld%18lld",k1,k2);
}
}
T6 树的重心
CCF非专业级植树能力认证
先以1号点为根把树提起来,然后对每个节点,统计它对答案的贡献。
设(i)的最大儿子为(s[i][0]),次大为(s[i][1]),以(i)为根的子树大小为(siz[i])。
设切掉的边两端更低的一个点为(u),当前讨论到节点(p)。
若(u)为(p)的祖先(含(p)),则(p)为重心当且仅当(2 imes siz[s[p][0]]leqslant s[u]leqslant 2 imes s[p])
若(u)在(p)的最大儿子的子树中,则(p)为重心当且仅当(max(siz[s[p][1]],siz[s[p][0]]-siz[u],siz[1]-siz[p]) imes 2leqslant siz[1]-siz[u])
若(u)在(p)的其它儿子的子树中,则(p)为重心当且仅当(max(siz[s[p][0]],siz[1]-siz[p]) imes 2leqslant siz[1]-siz[u])
否则,(p)为重心当且仅当(max(siz[s[p][0]],siz[1]-siz[p]-siz[u]) imes 2leqslant siz[1]-siz[u]) *这种情况可以用非子树减去祖先来求
化简一下,用主席树维护子树,用倍增维护一下祖先。
code:
#include<cstdio>
int Last[300002],Next[600002],End[600002],f[300002][22];
int t[300002],h[300002],s[300002],ss[300002][2],tot,T,n;
long long d[300002],ans;
inline int Max(int a,int b){return a>b?a:b;}
struct node{
node *ls,*rs;
int cnt;
}tr[8000002],*null=tr,*top,*rt[300002];
void init(){
null->ls=null->rs=null;
null->cnt=0;rt[0]=null;
}
node *addnode(){
node *p=++top;
p->ls=p->rs=null;
p->cnt=0;
return p;
}
void add(int p,int l,int r,node *t,node *lt){
t->ls=lt->ls;t->rs=lt->rs;t->cnt=lt->cnt+1;
if(l==r)return;
if(l+r>>1>=p)add(p,l,l+r>>1,t->ls=addnode(),lt->ls);
else add(p,l+r+2>>1,r,t->rs=addnode(),lt->rs);
}
int get(int L,int R,int l,int r,node *lt,node *t){
if(L<=l&&r<=R)return t->cnt-lt->cnt;
if(L>R||lt==t)return 0;
if(L<1)L=1;if(R>n)R=n;
long long ans=0;
if(l+r>>1>=L)ans+=get(L,R,l,l+r>>1,lt->ls,t->ls);
if(l+r>>1<R)ans+=get(L,R,l+r+2>>1,r,lt->rs,t->rs);
return ans;
}
void dfs1(int p,int F){
d[p]=d[F]+1;
f[p][0]=F;
s[p]=1;h[p]=0;
ss[p][0]=ss[p][1]=0;
for(int i=1;i<=19;i++)f[p][i]=f[f[p][i-1]][i-1];
for(int i=Last[p];i;i=Next[i])if(End[i]!=F){
dfs1(End[i],p);s[p]+=s[End[i]];
if(s[End[i]]>s[h[p]])h[p]=End[i];
if(s[End[i]]>ss[p][1])ss[p][1]=s[End[i]];
if(ss[p][1]>ss[p][0])ss[p][1]^=ss[p][0]^=ss[p][1]^=ss[p][0];
}
}
void dfs2(int p){
t[p]=++tot;
add(s[p],1,n,rt[t[p]]=addnode(),rt[t[p]-1]);
if(h[p])dfs2(h[p]);
for(int i=Last[p];i;i=Next[i])if(End[i]!=f[p][0]&&End[i]!=h[p])dfs2(End[i]);
if(p!=1){
int l=p,r=p,lb=2*s[p],rb=2*ss[p][0];
for(int i=19;i>=0;i--){
if(f[l][i]&&s[f[l][i]]<=lb)l=f[l][i];
if(f[r][i]&&s[f[r][i]]<rb)r=f[r][i];
}if(s[r]<rb&&r!=1)r=f[r][0];
if(s[l]<=lb&&l!=1)l=f[l][0];
ans+=(d[r]-d[l])*p;
}
}
void dfs3(int p){
for(int i=Last[p];i;i=Next[i])if(End[i]!=f[p][0])dfs3(End[i]);
if(h[p]){
ans+=1ll*p*get(1,s[1]-2*Max(ss[p][0],s[1]-s[p]),1,n,rt[t[h[p]]+s[h[p]]-1],rt[t[p]+s[p]-1]);
ans+=1ll*p*get(2*ss[p][0]-s[1],s[1]-2*Max(ss[p][1],s[1]-s[p]),1,n,rt[t[p]],rt[t[h[p]]+s[h[p]]-1]);
}if(p!=1){
ans+=1ll*p*get(s[1]-2*s[p],s[1]-2*ss[p][0],1,n,rt[0],rt[t[p]-1]);
ans+=1ll*p*get(s[1]-2*s[p],s[1]-2*ss[p][0],1,n,rt[t[p]+s[p]-1],rt[n]);
int l=f[p][0],r=f[p][0],lb=s[1]-2*ss[p][0],rb=s[1]-2*s[p];
for(int i=19;i>=0;i--){
if(f[l][i]&&s[f[l][i]]<=lb)l=f[l][i];
if(f[r][i]&&s[f[r][i]]<rb)r=f[r][i];
}if(s[r]<rb)r=f[r][0];
if(s[l]<=lb)l=f[l][0];
ans-=(d[r]-d[l])*p;
}
}
int main(){
init();
scanf("%d",&T);
while(T--){
scanf("%d",&n);top=tr;d[0]=0;ans=0;tot=0;
for(int i=1;i<=n;i++)Last[i]=0;
for(int i=1;i<n+n-2;i+=2){
scanf("%d%d",&End[i+1],&End[i]);
Next[i]=Last[End[i+1]];Last[End[i+1]]=i;
Next[i+1]=Last[End[i]];Last[End[i]]=i+1;
}dfs1(1,0);dfs2(1);dfs3(1);printf("%lld
",ans);
}
}
题出的好!难度适中,覆盖知识点广,题目又着切合实际的背景,解法比较自然。给出题人点赞 !