Skip to content

0430 C组模拟赛 题解

传送门

T1 题解

题目说漏了,保证数组所有数都为正整数。

思考一下,首先最大的子段和肯定是整个数组,对于一个长度为 $l$ 的子段,删除两端其中·一个数得到的两个长度为 $l-1$ 的子段的和肯定小于原先的那个子段(显而易见,因为都是正整数)。

容易想到用一个平衡树(set),维护子段的左右端点(闭区间)和子段和,按子段和为第一关键字,左右端点为第二、第三关键字排序。最初压入整个数组(即最大的子段),每次取出堆顶的数后再把删除两端得到的新子段分别压入。每次压入时会去重所以不用担心重复的问题。这样做必然会先找到较大的子段后再找到较小的,原因上面已经证了。

code

code
#include<iostream>
#include<algorithm>
#include<string.h>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#include<vector>
#include<set>
#define pbk(a) emplace_back(a) 
#define forup(i,s,e) for(ll i=(s);i<=(e);i++)
#define fordown(i,s,e) for(ll i=(s);i>=(e);i--)
using namespace std;
#define gc getchar()
inline ll read(){
    ll x=0,f=1;char c;
    while(!isdigit(c=gc)) if(c=='-') f=-1;
    while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
    return x*f;
}
#undef gc
const ll N=1e5+5,inf=0x3f3f3f3f;
ll n,k,a[N],sum;
struct Node{
    ll l,r,val;
    bool operator <(const Node &p)const{
        if(val!=p.val) return val>p.val;
        if(l!=p.l) return l<p.l;
        return r<p.r;
    }
};
set<Node> q;
signed main(){
     n=read();k=read();
     forup(i,1,n){
        a[i]=read();sum+=a[i];
     }
     q.insert(Node{1,n,sum});
     forup(i,1,k){
        set<Node>::iterator it=q.begin();
        ll val=it->val,l=it->l,r=it->r;
        q.erase(it);
        printf("%lld ",val);
        if(l<r){
            q.insert(Node{l+1,r,val-a[l]});
            q.insert(Node{l,r-1,val-a[r]});
        }
     }
}

T2 题解

很显然这是一道树形 dp。

设 $dp_{u,i}$ 表示考虑以点 $u$ 为根的子树,点 $u$ 取 $i$ 的方案数,有一个显然的转移方程。

$$dp_{u,i}=\prod\limits_{v \in children(u)}(\sum\limits_{1 \le j\le m,\left|j-i\right|\ge k}dp_{v,j})$$

我们直接 $O(nm^2)$ 套这个方程可以得 $20pts$.

注意到合法的 $j$ 是连续的两段,我们维护一个前缀和,复杂度变为 $O(nm)$ 就可以得 $40pts$。

假如我们打个表,容易发现,对于一个结点 $u$,有两个性质:

  • $dp_1=dp_m,dp_2=dp_{m-1}\dots$(即 dp 值是对称的)
  • dp 值的中间有一段是相等的。

这非常好理解,首先对称性很显然,因为具体大小不影响 dp 值,只有与上下界的距离影响。结论二因为叶子结点的 dp 值全为 $1$,所以叶子结点的父亲除去开头 $k$ 个和末尾 $k$ 个以外其他值都是相等的,再往上一层除去开头 $2k$ 个和末尾 $2k$ 个都是相等的,同理可以推到根节点。

通过这个证明,容易发现另一个性质:

  • dp 值至多开头和结尾分别有 $k\cdot(n-1)$ 个与中间不相等。

所以我们可以只存开头 $k\cdot(n-1)$ 个值,中间只用存一个,转移也只算这 $k \cdot (n-1)+1$ 个值,复杂度 $O(n^2k)$。

另外我小数据用暴力过的,因为我不知道怎么把这个结论用在数据较小(指 $m < k\cdot(n-1)$)的情况。

code(比较屎山)

code
#include<iostream>
#include<algorithm>
#include<string.h>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#include<vector>
#define pbk(a) emplace_back(a) 
#define forup(i,s,e) for(ll i=(s);i<=(e);i++)
#define fordown(i,s,e) for(ll i=(s);i>=(e);i--)
using namespace std;
#define gc getchar()
inline ll read(){
    ll x=0,f=1;char c;
    while(!isdigit(c=gc)) if(c=='-') f=-1;
    while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
    return x*f;
}
#undef gc
const ll N=105,M=1e5+5,mod=1e9+7;
ll t,n,m,k,len;
vector<ll> e[N];
ll dp[N][M],pre[N][M];
void dfs(ll x,ll fa){
    forup(i,1,len){
        dp[x][i]=1;
    }
    for(auto v:e[x]){
        if(v==fa) continue;
        dfs(v,x);
        forup(i,1,len){
            int res=0;
            if(i>k){
                res=(res+pre[v][i-k])%mod;
            }
            if(i+k<=m){
                int bk=m-(i+k-1);
                if(k==0) bk--;
                if(bk<=len){
                    res=(res+pre[v][bk])%mod;
                }else if(i+k<=len){
                    res=(res+(pre[v][len-1]+mod-pre[v][i+k-1])%mod)%mod;
                    res=(res+pre[v][len-1])%mod;
                    res=(res+dp[v][len]*(m-len*2+2)%mod)%mod;
                }else{
                    res=(res+pre[v][len])%mod;
                    res=(res+dp[v][len]*(bk-len)%mod)%mod;
                }
            }
            dp[x][i]=dp[x][i]*res%mod;
        }
    }
    forup(i,1,len){
        pre[x][i]=(pre[x][i-1]+dp[x][i])%mod;
    }
}
void dfs1(int x,int fa){
    forup(i,1,m){
        dp[x][i]=1;
    }
    for(auto i:e[x]){
        if(i==fa) continue;
        dfs1(i,x);
        forup(j,1,m){
            ll res=0;
            if(j>=k){
                res=(res+pre[i][j-k])%mod;
            }if(j+k<=m){
                if(k!=0){
                    res=(res+pre[i][m]+mod-pre[i][j+k-1])%mod;
                }else{
                    res=(res+pre[i][m]+mod-pre[i][j+k])%mod;
                }
            }
            dp[x][j]=(dp[x][j]*res)%mod;
        }
    }
    forup(i,1,m){
        pre[x][i]=(pre[x][i-1]+dp[x][i])%mod;
    }
}
signed main(){
    t=read();
    while(t--){
        n=read();m=read();k=read();
        mem(dp,0);mem(pre,0);
        forup(i,1,n){
            e[i].clear();
        }
        forup(i,1,n-1){
            ll u=read(),v=read();
            e[u].push_back(v);e[v].push_back(u);
        }
        if(m>10000){
            len=min((m+1)/2,(n-1)*k+1);
            dfs(1,0);
            printf("%lld\n",(pre[1][len-1]*2%mod+dp[1][len]*(m-len*2+2)%mod)%mod);          
        }else{
            dfs1(1,0);
            printf("%lld\n",pre[1][m]);
        }
    }
}

T3 题解

这道题给了很多部分分,每个部分分都有对应的做法,但是我只讲正解。

首先我们很好求以每个点为右下角的最大的正方形。

做法是这样的,设 $f_{i,j}$ 表示以点 $(i,j)$ 为右下角的正方形的最大可能边长,易得 dp 式子:

$$\forall a_{i,j}=0,f_{i,j}=0$$ $$\forall a_{i,j}=1,f_{i,j}=\min{f_{i-1,j},f_{i,j-1},f_{i-1,j-1}}+1$$

这个式子很显然就不说了,预处理复杂度 $O(n^2)$。

然后考虑如何求答案。发现可以二分答案,假如在 $(x_1,y_1)(x_2,y_2)$ 之间答案为 $r$,那么必定 $\exists(i,j),x_1+r \le i \le x_2,y_1+r \le j \le y_2,f_{i,j} \ge r$。这是显然的,因为首先必须要有一个 $f \ge r$,其次假如这个点太靠边那正方形就不完全在询问矩阵内了。

但是这样的复杂度 $O(n^2 \log n)$ 显然过不了,注意到我们只需要查询矩阵内最大的 $f$,这是一个二维 $rmq$,可以 $O(n^2\log^2n)$ 预处理,$O(1)$ 查询。

故总复杂度为 $O(n^2+n^2\log^2n+T)$,注意常数优化。

code

code
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
using namespace std;
#define gc getchar()
inline int read(){
    int x=0,f=1;char c;
    while(!isdigit(c=gc)) if(c=='-') f=-1;
    while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
    return x*f;
}
#undef gc
const int N=1005,LN=15,inf=0x3f3f3f3f;
int n,m,a[N][N],f[N][N],qq;
int lg[N];
struct ST{
    int g[LN][LN][N][N];
    void init(){
        forup(i,1,n){
            forup(j,1,m){
                g[0][0][i][j]=f[i][j];
            }
        }
        for(int ln=0;(1<<ln)<=n;++ln){
            int nn=max(ln-1,0),sn=1<<nn;
            if(ln==0) sn=0;
            for(int lm=0;(1<<lm)<=m;++lm){
                int nm=max(lm-1,0),sm=1<<nm;
                if(lm==0) sm=0;
                if(lm==0&&ln==0) continue;
                forup(i,1,min(n,n-sn*2+1)){
                    forup(j,1,min(m,m-sm*2+1)){
                        g[ln][lm][i][j]=max({g[nn][nm][i][j],g[nn][nm][i+sn][j],g[nn][nm][i][j+sm],g[nn][nm][i+sn][j+sm]});
                    }
                }
            }
        }
    }
    int ask(int x1,int y1,int x2,int y2){
        int ln=lg[x2-x1+1],lm=lg[y2-y1+1],sn=1<<ln,sm=1<<lm;
        return max({g[ln][lm][x1][y1],g[ln][lm][x2-sn+1][y1],g[ln][lm][x1][y2-sm+1],g[ln][lm][x2-sn+1][y2-sm+1]});
    }
}mst;
signed main(){
    n=read(),m=read();
    forup(i,2,max(n,m)){
        lg[i]=lg[i>>1]+1;
    }
    forup(i,1,n){
        forup(j,1,m){
            a[i][j]=read();
            if(a[i][j]==0) f[i][j]=0;
            else f[i][j]=min({f[i-1][j],f[i][j-1],f[i-1][j-1]})+1;
        }
    }
    mst.init();
    qq=read();
    forup(i,1,qq){
        int x1=read(),y1=read(),x2=read(),y2=read();
        int ll=0,rr=min(x2-x1+1,y2-y1+1),mm;
        while(ll<rr){
            mm=(ll+rr+1)>>1;
            if(mst.ask(x1+mm-1,y1+mm-1,x2,y2)>=mm) ll=mm;
            else rr=mm-1;
        }
        printf("%d\n",ll);
    }
}

T4 题解

这题不难。

首先,这条最短路一定是以某个特殊城市开始,以某个特殊城市结束的,不然它必定不优。容易得到 $40pts$ 做法,对于每个特殊城市跑一遍 dijkstra,除了距离外,设 $dp_{i,j}$ 表示 $i$ 到 $j$ 的最短路至多经过多少个特殊城市,最后统计 $dp_{i,j}=k$ 的最小距离即可。复杂度 $O(n^2\log n)$(堆优化)。

考虑能否快速找到正解的起始点,发现假如我们对任意一个特殊点跑 dijkstra,距离它最远的点即为一个可行的起点,因为如果从其它点开始就要往那边绕一圈,那必定不优(感性理解一下),复杂度为 $O(n\log n)$。

code

code
#include<iostream>
#include<algorithm>
#include<string.h>
#define mem(a,b) memset(a,b,sizeof(a))
#include<queue>
#include<vector>
#define pbk(a) emplace_back(a) 
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
using namespace std;
#define gc getchar()
inline int read(){
    int x=0,f=1;char c;
    while(!isdigit(c=gc)) if(c=='-') f=-1;
    while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
    return x*f;
}
#undef gc
const int N=1e5+5,inf=0x3f3f3f3f;
int n,m,K,a[N];
struct edge{
    int v,w;
};
vector<edge> e[N];
struct Node{
    int dis,u;
    bool operator <(const Node &p)const{return dis>p.dis;}
};
int dist[N],vis[N],dp[N],nece[N];
priority_queue<Node> q;
void dij(int rt){
    while(q.size()) q.pop();
    mem(vis,0);mem(dist,0x3f);
    mem(dp,0);
    dist[rt]=0;
    q.push(Node{0,rt});
    while(q.size()){
        int u=q.top().u;q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        if(nece[u]) dp[u]++;
        for(auto i:e[u]){
            int v=i.v,w=i.w;
            if(dist[v]>=dist[u]+w){
                dist[v]=dist[u]+w;
                q.push(Node{dist[v],v});
                dp[v]=max(dp[v],dp[u]);
            }
        }
    }
}
signed main(){
    n=read();m=read();
    forup(i,1,m){
        int u=read(),v=read(),w=read();
        e[u].push_back(edge{v,w});
        e[v].push_back(edge{u,w});
    }
    K=read();
    forup(i,1,K){
        a[i]=read();
        nece[a[i]]=1;
    }
    dij(a[1]);
    int mmm=1;
    forup(i,1,K){
        if(dist[a[i]]>dist[a[mmm]]) mmm=i;
    }
    dij(a[mmm]);
    int ans=inf; 
    forup(i,1,K){
        if(dp[a[i]]==K) ans=min(ans,dist[a[i]]);
    }
    printf("%d",ans);
}

Comments