2021百度之星复赛部分题解

2021百度之星复赛部分题解

T1

\(n,m\leq 2\)的情况需要特殊讨论

其余的,\(m\)为奇数时是二分图,一定成立

\(m\)为偶数时,只有\(n=m\)​成立

int n,m;
int main(){ 
    rep(_,1,rd()) {
        n=rd(),m=rd();
        if(n>1 && (m&1 || (n>2 && m==n))) {
            puts("Yes!");
            continue;
        }
        puts("No!");
    }
}

T2

有点难度的计数题,将序列剖分成相同符号的连续段,每段个数为\(a_i\)

则按照+号和*谁开头谁末尾分成4类讨论,再枚举分段数

即可把问题转化为若干个这样的子问题: 把+号分成\(x\)段,把*号分成\(y\)

容易发现这就是盒子有序的第二类斯特林数,可以\(O(n^2)\)预处理,\(O(n)\)​完成一组询问的查询

const int N=6010,P=1e9+7;
int n,m;
int S[N][N];
int main(){ 
    S[0][0]=1;
    rep(i,1,N-1) rep(j,1,i) S[i][j]=1ll*j*(S[i-1][j-1]+S[i-1][j])%P;
    // 盒子有序的斯特林数
    rep(_,1,rd()) {
        n=rd(),m=rd();
        int ans=0;
        rep(i,1,min(n,m)+1) {
            ans=(ans+2ll*S[n][i]*S[m][i]%P+1ll*S[n][i]*S[m][i-1]+1ll*S[n][i]*S[m][i+1])%P;
        }
        printf("%d\n",ans);
    }
}

T3

贪心题真的搞心态,开始写了一个堆贪心又T又wa

首先考虑\(b_i=1\)的简单版本,此时容易发现就是最大化连边的数量

每个点优先向儿子连,有多再丢给父亲即可,可以用一个dfs贪心处理

一般的情况,可以做一个转化得到一个类似的问题:

每个点有\(b_i\)个,最多连\(p_i\)\(\Longrightarrow\) 每个点最多连\(b_i\cdot p_i\)条边

一条边\((u,v)\)可以连\(\min\{b_u,b_v\}\)

将容易发现转化后一种连边方案最终总可以构造得到一组合法的连边方案

如上,同样可以通过dfs来得到答案,为总点数-连边数

const int N=4e5+10,INF=1e9+10;

int n,m;
struct Edge{
    int to,nxt;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v) {
    e[++ecnt]=(Edge){v,head[u]};
    head[u]=ecnt;
}
ll a[N],c[N],ans;
void dfs(int u,int f) {
    for(int i=head[u];i;i=e[i].nxt) {
        int v=e[i].to;
        if(v==f) continue;
        dfs(v,u);
        ll w=min(min(a[u],a[v]),min(c[u],c[v]));
        c[u]-=w,c[v]-=w,ans-=w;
    }
}

int main() {
    rep(_,1,rd()) {
        n=rd(),ecnt=0;
        rep(i,1,n) head[i]=0;
        rep(i,2,n) {
            int u=rd(),v=rd();
            AddEdge(u,v),AddEdge(v,u);
        }
        ans=0;
        rep(i,1,n) {
            int b=rd(),p=rd();
            ans+=a[i]=b,c[i]=1ll*b*p;
        }
        dfs(1,0);
        printf("%lld\n",ans);
    }
}

T4

是一个经典的dp优化题,可以预处理树上两点距离

先考虑对于一个给定序列的求解,容易发现是一个经典的区间dp的问题

这类问题想要优化复杂度,最常见的办法是决策单调性

感性理解可以直接套用四边形不等式进行决策优化,在\(O(n^2)\)时间内求解

回到原题,发现这些dp会出现共用一部分dp答案的情况

我们给每一个元素定一个编号,原先\(dp_{l,r}\)\(l,r\)的编号替换为\(dp_{i,j}\)

这样每次插入一个元素,计算以这个元素\(j\)为结尾,序列前面每一个\(i\)所对应的\(dp_{i,j}\)

同时维护区间总和,区间决策点(用于决策单调性)

访问前面每一个位置可以通过记录一个前驱来实现,这样就能完成共用dp答案

复杂度为\(O(m^2+(n+r)^2)\)​,由于数组访问比较不连续,常数较大

const int N=1510,M=300+N,INF=1e9+10;

int n,m,q;
struct Edge{
    int to,nxt,w;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v,int w) {
    e[++ecnt]=(Edge){v,head[u],w};
    head[u]=ecnt;
}

ll dis[N][N];
void dfs(int rt,int u,int f) {
    for(int i=head[u];i;i=e[i].nxt) {
        int v=e[i].to;
        if(v==f) continue;
        dis[rt][v]=dis[rt][u]+e[i].w;
        dfs(rt,v,u);
    }
}

ll ans[M],dp[M][M],s[M][M];
// ans为答案,dp[i][j]为区间dp数组
// s[i][j] 为区间距离总和
int g[M][M];
// g[i][j]为dp决策点
int pre[M],v[M]; // v记录这个元素的值,pre记录前驱
void Get(int i,int f,int x) { 
    pre[i]=f,v[i]=x;
    dp[i][i]=s[i][i]=0,g[i][i]=-1;
    ll sum=0;
    for(int j=f,lst=i;~j;lst=j,j=pre[j]) {
        sum+=2*dis[v[j]][x];
        s[i][j]=s[f][j]+sum;
        // 四边形不等式优化dp
        int l=~g[i][lst]?g[i][lst]:i,r=~g[f][j]?g[f][j]:lst;
        ll mi=9e18;
        g[i][j]=-1;
        for(int k=l;;k=pre[k]) {
            ll v=dp[i][k]+dp[pre[k]][j];
            if(mi>v) mi=v,g[i][j]=k;
            if(k==r) break;
        }
        dp[i][j]=mi+s[i][j];
        ans[i]=dp[i][j];
    }
}

int main(){ 
    rep(_,1,rd()) {
        n=rd(),m=rd(),q=rd();
        rep(i,0,n) head[i]=0;
        ecnt=0;
        rep(i,1,n) {
            int f=rd(),w=rd();
            AddEdge(i,f,w),AddEdge(f,i,w);
        }
        rep(i,0,n) dfs(i,i,-1);
        v[1]=rd(),pre[1]=-1,ans[1]=dp[1][1]=s[1][1]=0,g[1][1]=-1;
        rep(i,2,m) Get(i,i-1,rd());
        printf("%lld\n",ans[m]);

        rep(i,m+1,m+q) {
            int f=rd()+m,x=rd();
            Get(i,f,x);
            printf("%lld\n",ans[i]);
        }
    }
}

T5

由带入T2代码可以得到,最劣情况下,本质不同的方案数达到\(3\cdot 10^5\)

即便数据随机也有大量极限情况,而且枚举有常数,故不可以通过

实际上可以直接meet in the middle

枚举前5个数,后五个数,方案数为\(\binom{10}{5}=252\)

再搜索两部分的答案,第一部分带入\(x\),第二部分初始带入0,最坏情况下两边各有46种

最终的答案就是 : 第一部分答案 \(\cdot\) 第二部分乘积+第二部分答案

枚举之后已经确定了乘积,故两部分是分离的,只需要相加即可

相加求最小值可以尺取进行,枚举的情况上界很松,实际极限复杂度达不到\(252\cdot 46\cdot \log\)

因此较大常数的实现也可以在1s左右出解

int n,m,ans;
char op[N];
int v[N];
int S,bin[1<<10];
int Mod(int x){
    return x>=P?x-P:x;
}

int st[100000],c,st2[100000],c2;
void dfs(int S,int x,int lst1,int lst2) {
    if(!S) return st[++c]=x,void();
    for(int T=S;T;T&=T-1) {
        int i=bin[T&-T];
        if(op[i]=='+' && lst1<i) dfs(S-(1<<i),Mod(x+v[i]),i,-1);
        if(op[i]=='*' && lst2<i) dfs(S-(1<<i),1ll*x*v[i]%P,-1,i);
    }
}

int cnt[1<<10];
int X[6][500],Xc[6];
int Y[6][500],Yc[6];
const int A=1023;

bool Med;
int main(){ 
    rep(i,0,9) bin[1<<i]=i;
    rep(i,1,A) cnt[i]=cnt[i&(i-1)]+1;
    rep(_,1,rd()) {
        ans=P;

        n=rd(),m=rd();
        rep(i,0,n-1) {
            while(op[i]=getchar(), op[i]!='*' && op[i]!='+');
            v[i]=rd();
        }
		
        // 这里是我写蠢了,直接枚举10个数的子集,大小为5的讨论就好了。。
        int S=0;
        rep(i,0,n-1) if(op[i]=='*') S|=1<<i;
        rep(i,0,n) Xc[i]=Yc[i]=0;
        for(int T=S;;T=(T-1)&S) {
            if(cnt[T]>5) continue;
            X[cnt[T]][++Xc[cnt[T]]]=T;
            if(!T) break;
        }
        S=A^S;
        for(int T=S;;T=(T-1)&S) {
            if(cnt[T]>5) continue;
            Y[cnt[T]][++Yc[cnt[T]]]=T;
            if(!T) break;
        }
        rep(i,0,5) {
            rep(a,1,Xc[i]) rep(b,1,Yc[5-i]) {
                int x=X[i][a],y=Y[5-i][b];
                int w=1;
                rep(i,0,n-1) if(x&(1<<i)) w=1ll*w*v[i]%P;
                c=0;
                dfs(A^(x|y),m,-1,-1);
                rep(i,1,c2=c) st2[i]=1ll*st[i]*w%P;
                c=0;
                dfs(x|y,0,-1,-1);
                sort(st+1,st+c+1),sort(st2+1,st2+c2+1);
                int p=1;
                drep(i,c,1) {
                    cmin(ans,st[i]+st2[1]);
                    while(p<c2 && st[i]+st2[p]<P) p++;
                    cmin(ans,(st[i]+st2[p])%P);
                }
            }
        }

        printf("%d\n",ans);
    }
}
posted @ 2021-08-23 10:58  chasedeath  阅读(600)  评论(0编辑  收藏  举报