2.21+2.22考试总结

连续两天数组开小,\(D1T1\ 30+D2T2\ 60+D2T4\ 10\),一旦数组开大就 \(A\)\(qwq\)

Day 1

T1 排序

题目大意:

给出一个长度为 \(4n\) 的序列 \(a\),要求将其配对为 \(n\) 个四元组 \(x_i,y_i,z_i,w_i\),求 \(\max\sum\limits_{i=1}^n |x_iy_i-z_iw_i|\)


难度:三星(满分十星)

发现绝对值没有意义,问题就变成了 \(\max(\sum\limits_{i=1}^nx_iy_i-\sum\limits_{i=1}^nz_iw_i)\),即 \(\max\sum\limits_{i=1}^nx_iy_i-\min\sum\limits_{i=1}^nz_iw_i\)

那么将 \(a\) 从小到大排序,前 \(2n\) 个为 \(z_i,w_i\),后 \(2n\) 个为 \(x_i,y_i\)

发现一个性质:

  • \(x<y<z<w\),则 \(xy+zw>xz+yw>xw+yz\)

所以,答案可以改写为:

\[\sum\limits_{i=1}^na_{2n+2i-1}a_{2n+2i}-\sum\limits_{i=1}^na_ia_{2n-i+1} \]

时间复杂度 \(O(n\log n)\)

注意开 \(ll\)(实际上应该开 \(ull\),但数据太水)

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
int n;ull ans,a[400005];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=4*n;i++)
        cin>>a[i];
    sort(a+1,a+4*n+1);
    for(int i=n*2+1;i<=n*4;i+=2)
        ans+=a[i]*a[i+1];
    for(int i=1;i<=n;i++)
        ans-=a[i]*a[2*n+1-i];
    cout<<ans;
    return 0;
}

T2 牛吃草

题目大意:

有一个数列 \(w\)\(w_i\) 表示假如选择这个点,他最大可以覆盖到 \(i-w_i+1\)。同时,覆盖面积不能重叠。求在 \(\frac{ns}{100}\le\) 总覆盖面积的情况下,每个被选择的点中覆盖面积最小值的最大值。


难度:五星半

这到第二题很不正常……

最小值最大,考虑二分答案。

直接二分 \(ans\),验证总覆盖面积是否合法。

容易看出可以 \(dp\)

设状态 \(dp_i\) 表示枚举到第 \(i\) 位时,已确定的总覆盖面积的最大值。

若当前检查答案为 \(x\),则有转移方程:

\[dp_i=\max(\max\limits_{j=i-w_i}^{i-x}(dp_j+i-j),dp_{i-1}) \]

由于 \(w_i-1\le w_{i-1}\),所以

\[i-w_i\ge(i-1)-w_{i-1} \]

所以决策区间的左端点一定单调不减。同样,右端点单调递增。

发现决策区间即为一个滑动窗口,单调队列优化 \(dp\) 即可。

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;int q[N],fr,bk;
int n,s,dp[N],w[N],l=1,r,ans;
int check(int x){
    fr=1;bk=0;
    q[++bk]=0;
    for(int i=1;i<=n;i++)
        dp[i]=0;
    for(int i=1;i<=n;i++){
        dp[i]=dp[i-1];
        while(fr<=bk&&q[fr]<i-w[i]) ++fr;
        if(i-x+1<0) continue;
        while(fr<=bk&&dp[q[bk]]-q[bk]<dp[i-x+1]-i+x-1) --bk;
        if(fr<=bk&&i-q[fr]>=x)
            dp[i]=max(dp[i-1],dp[q[fr]]+i-q[fr]);
        if(dp[i]*100>=s) return 1;
        q[++bk]=i-x+1;
    }return 0;
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>w[i];
    cin>>s;
    s*=n;r=n;
    while(l<=r){
        int mid=(l+r)/2;
        if(!check(mid)) r=mid-1;
        else ans=mid,l=mid+1;
    }cout<<ans;
    return 0;
}

T3 树上的宝藏

题目大意:

有一棵树,一条边两端最多只能选一个。现在可以修改任意一条边,使这条边两端最少选一个。问对于每条边修改后,有多少种可能性。


难度:六星(满分十星)

发现可以树形 \(dp\)

设状态 \(dp_{0/1,i}\) 表示选 \(/\) 不选 \(i\) 号点时,其子树内的可能情况数量。

那么有转移方程:

\[dp_{0,u}=\prod\limits_{v\in uson}dp_{1,v} \\\ \\dp_{1,u}=\prod\limits_{v\in uson}(dp_{0,v}+dp_{1,v}) \]

再搞一搞可以拿到 \(60pts\)

当然,既然树形 \(dp\) 解决不了,那就升个级,换根 \(dp\)!!!

设状态 \(dp_{2/3,i}\) 表示选 \(/\) 不选 \(i\) 号点时,其子树外的可能情况数量。

那么有转移方程:

\[dp_{2,u}=dp_{3,fa}\ \frac{dp_{1,fa}}{dp_{0,u}+dp_{1,u}} \\\ \\dp_{3,u}=dp_{2,u}+dp_{2,fa}\ \frac{dp_{0,fa}}{dp_{1,u}} \]

那么我们发现,假如将边靠近根节点的一端称为 \(u\),另一端称为 \(v\),则可以将问题看为以 \(u\) 为根节点,统计答案。设 \(t_1\) 为选择 \(u\)\(t_2\) 为不选 \(u\),那么有:

\[t_1=dp_{2,u}(dp_{0,v}+dp_{1,v})\prod\limits_{w\ne v,w\in uson}dp_{1,w} \\\ \\ t_2=dp_{3,u}\ dp_{0,v}\prod\limits_{w\ne v,w\in uson}(dp_{0,w}+dp_{1,w}) \]

时间复杂度 \(O(n^2)\),实际上是错的,但是跑得比正解还快,所以提一嘴。

实际上应该这样写:

\[t_1=dp_{2,u}\ \frac{dp_{0,u}\ (dp_{0,v}+dp_{1,v})}{dp_{1,v}}\\\ \\ t_2=dp_{3,u}\ \frac{dp_{1,u}\ dp_{0,v}}{dp_{0,v}+dp_{1,v}} \]

由于逆元,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll p=998244353,N=3e5+5;
int n,m,h[N],nxt[N*2],to[N*2],id[N*2];
int fa[N],sn[N],fat[N];ll dp[4][N];
ll qpow(ll x,ll y){
    ll re=1;
    while(y){
        if(y&1)
            re=re*x%p;
        x=x*x%p;
        y>>=1;
    }return re;
}void ad(int x,int y,int ij){
    to[++m]=y;
    id[m]=ij;
    nxt[m]=h[x];
    h[x]=m;
}void dfs1(int x,int f){
    fat[x]=f;
    dp[0][x]=dp[1][x]=1;
    for(int i=h[x];i;i=nxt[i]){
        int y=to[i];
        if(y==f) continue;
        fa[id[i]]=x;
        sn[id[i]]=y;
        dfs1(y,x);
        dp[0][x]=dp[0][x]*dp[1][y]%p;
        dp[1][x]=dp[1][x]*(dp[0][y]+dp[1][y])%p;
    }
}void dfs2(int x){
    dp[2][x]=dp[3][fat[x]]*dp[1][fat[x]]%p*qpow(dp[0][x]+dp[1][x],p-2)%p;
    dp[3][x]=dp[3][fat[x]]*dp[1][fat[x]]%p*qpow(dp[1][x]+dp[0][x],p-2)%p;
    dp[3][x]=(dp[3][x]+dp[2][fat[x]]*dp[0][fat[x]]%p*qpow(dp[1][x],p-2)%p)%p;
    if(x==1) dp[2][x]=dp[3][x]=1;
    for(int i=h[x];i;i=nxt[i])
        if(to[i]!=fat[x]) dfs2(to[i]);
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1,x,y;i<n;i++)
        cin>>x>>y,ad(x,y,i),ad(y,x,i);
    dfs1(1,0);
    dfs2(1);
    for(int i=1;i<n;i++){
        int x=fa[i],y=sn[i];
        ll t1=dp[2][x],t2=dp[3][x];
        t1=t1*dp[0][x]%p*qpow(dp[1][y],p-2)%p*(dp[0][y]+dp[1][y])%p;
        t2=t2*dp[1][x]%p*qpow(dp[0][y]+dp[1][y],p-2)%p*dp[0][y]%p;
        cout<<((t1+t2)%p+p)%p<<"\n";
    }return 0;
}

T4 MEX

历史求和线段树,全机房都没研究出来,先咕咕咕了……

Day 2

T1 打赌

题目大意:

给一个骰子,让他在一块 \(r\times c\) 的格子纸上来回翻滚(详见题目),求在每个格子上时,骰子上方点数之和。


难度:两星(满分十星)

记录六个面的数字,在同一行旋转时,先加上 \(14\times\lfloor \frac{c}{4} \rfloor\),然后模拟最后 \(m\mod 4\) 次翻滚即可。

时间复杂度 \(O(r)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll r,c,ans,a[7]={0,1,6,2,5,3,4};
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>r>>c;
    for(int i=1;i<=r;i++){
        ans+=(c/4)*14;
        ll x=a[1];
        if(i%2){
            a[1]=a[5];
            a[5]=a[2];
            a[2]=a[6];
            a[6]=x;
        }else{
            a[1]=a[6];
            a[6]=a[2];
            a[2]=a[5];
            a[5]=x;
        }for(int j=1;j<=c%4;j++){
            x=a[1];
            if(i%2){
                a[1]=a[6];
                a[6]=a[2];
                a[2]=a[5];
                a[5]=x;
            }else{
                a[1]=a[5];
                a[5]=a[2];
                a[2]=a[6];
                a[6]=x;
            }ans+=a[1];
        }x=a[1];a[1]=a[4];
        a[4]=a[2];
        a[2]=a[3];
        a[3]=x;
    }cout<<ans;
    return 0;
}

T2 舞会

题目大意:

有以下四类人:

  • 想跟更高人跳舞的男生
  • 想跟更矮人跳舞的男生
  • 想跟更高人跳舞的女生
  • 想跟更矮人跳舞的女生

男生女生各有 \(n\) 个。问最多能配成几对。


难度:三星(满分十星)

考虑以下两个性质:

  1. 高配高,矮配矮优于高配矮,矮配高。
  2. 想跟更高人跳舞的男生只能和想跟更矮人跳舞的女生配对;想跟更高人跳舞的女生只能和想跟更矮人跳舞的男生配对。

根据此性质,对所有 \(2n\) 个人 从矮到高进行排序。

然后枚举所有人。定义两个队列:
\(q1:\) 存放想跟更高人跳舞的女生。
\(q2:\) 存放想跟更高人跳舞的男生。

在枚举时,假如遇到想跟更高人跳舞的人,加入相应的队列。假如遇到想跟更矮人跳舞的人,查看与之性别相反的队列的 \(front\) 是否小于他的身高。由于 \(front\) 是该队列中最矮的,而当前枚举到的人身后都比他高,所以贪心成立。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
int n,ans;queue<int>q1,q2;
struct peo{int h,id;}a[N];
int cmp(peo x,peo y){
    return x.h<y.h;
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].h;
        if(a[i].h<0){
            a[i].h=-a[i].h;
            a[i].id=1;
        }
    }for(int i=n+1;i<=2*n;i++){
        cin>>a[i].h;
        if(a[i].h<0){
            a[i].h=-a[i].h;
            a[i].id=3;
        }else a[i].id=2;
    }sort(a+1,a+2*n+1,cmp);
    for(int i=1;i<=2*n;i++){
        if(!a[i].id)
            q1.push(a[i].h);
        if(a[i].id==1)
            if(q2.size()&&a[i].h>q2.front())
                q2.pop(),ans++;
        if(a[i].id==2)
            q2.push(a[i].h);
        if(a[i].id==3)
            if(q1.size()&&a[i].h>q1.front())
                q1.pop(),ans++;
    }cout<<ans;
    return 0;
}

T3 最小生成树

题目大意

一张完全图,边 \((x,y)\) 边权为 \(\gcd(x,y)\)。求满足以下要求的生成树的个数:

  • 是一棵最小生成树。
  • 令其中一点为根,则该树满足小根堆性质。

难度:五星(满分十星)

显然能构造出边权皆为 \(1\) 的生成树,因此最小生成树所有边权必为 \(1\)

考虑从 \(1\)\(n\) 将点加入小根堆。

那么每次加入的点一定是加在叶子上,因此上一次小根堆的形状对这次加点的可能性没有影响。

发现每个点只能加在和他 \(\gcd\)\(1\) 的点下,学过数论的小朋友一定知道,加入 \(i\) 后,堆的形态可能性将 \(\times\varphi(i)\)

线性求 \(\varphi\) 值,答案即为 \(\prod\limits_{i=1}^n\varphi(i)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,p[20005],q[20005],vis[20005];
int id,ans;const int md=1e8+7;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;q[1]=1;ans=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]){
            p[++id]=i;
            q[i]=i-1;
        }for(int j=1;p[j]<=n/i;j++){
            vis[i*p[j]]=1;
            if(i%p[j])
                q[i*p[j]]=(p[j]-1)*q[i];
            else{
                q[i*p[j]]=p[j]*q[i];
                break;
            }
        }ans=(ll)ans*q[i]%md;
    }cout<<ans;
    return 0;
}

T4 买汽水

题目大意:

\(n\) 个数,从中选出任意多个,且选出的数之和 \(\le m\),问选出的数之和最大是多少。


难度:五星(满分十星)

思路简单易懂。

发现当 \(n\le 20\) 时,暴力 \(dfs\) 可过,而数据范围为 \(n\le 40\)

由于 \(\frac{40}{2}=20\),所以考虑将问题分成两个部分。

于是,对于 \([1,\lfloor \frac{n}{2}\rfloor]\) 这一部分进行暴搜,用数组 \(c\) 记录所有能够凑出来的值;\((\lfloor \frac{n}{2}\rfloor,n]\) 这部分的所有答案存入数组 \(d\)

发现问题转化为:求 \(max(c_i+d_j)\),且 \(c_i+d_j\le m\)

可推导出:在 \(c_i\) 固定时, \(d_j\) 越大越好,且 \(d_j\le m-c_i\)

所以 \(d_j\) 的值可以通过二分查找求得。

时间复杂度 \(O(2^{\lfloor\frac{n}{2}\rfloor}\log_2 2^{\lfloor\frac{n}{2}\rfloor})\),化简得 \(O(2^{\lfloor\frac{n}{2}\rfloor-1}n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,k,l,p[45];
ll m,s,c[1000005],d[1000005],ans;
unordered_map<ll,int>a,b;
void dfs1(int x){
    if(s>m) return;
    if(x>n/2){
        if(!a[s]){
            a[s]=1;
            c[++k]=s;
        }return;
    }dfs1(x+1);
    s+=p[x];
    dfs1(x+1);
    s-=p[x];
}void dfs2(int x){
    if(s>m) return;
    if(x>n){
        if(!b[s]){
            b[s]=1;
            d[++l]=s;
        }return;
    }dfs2(x+1);
    s+=p[x];
    dfs2(x+1);
    s-=p[x];
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>p[i];
    dfs1(1);dfs2(n/2+1);
    sort(d+1,d+l+1);
    for(int i=1;i<=k;i++){
        int j=upper_bound(d+1,d+l+1,m-c[i])-d-1;
        if(c[i]+d[j]<=m) ans=max(ans,c[i]+d[j]);
    }cout<<ans;
    return 0;
}
posted @ 2024-02-22 21:31  长安一片月_22  阅读(11)  评论(0编辑  收藏  举报