• 校内集训20181001


    $T1$(Loj2758):

    给你一个环和N个切口以及每个切口的位置$A_i$,你需要切三刀将环分成三份使得最小的一块最大。

    $Nleq10^5,A_ileq10^9$。

    $Solution$:

    “最小的一块最大”满足单调性,考虑二分答案然后$check(ans)$。

    因为是一个环,我们无法线性$check$,只能枚举第一刀的位置,然后后面每一刀尽量切在最小的$ans$的位置。

    在有序表中查找最小的大于等于某数的位置可以直接$lowerbound$,复杂度$O(N imes logN imes logN)$。

    $Code$:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    
    #define MAXN 100005
    #define MAXM 500005
    #define INF 0x7fffffff
    #define ll long long
    ll A[MAXN<<1],sum[MAXN<<1];
    ll N,L,R,ans;
    
    inline ll read(){
        ll x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    inline bool check(ll x){
        for(ll i=1;i<=N;i++){
            ll tp=sum[i-1];
            ll c1=lower_bound(sum+i,sum+i+N-1,x+tp)-sum;
            if(!c1 || c1>i+N-1 || sum[c1]<x+tp) continue;tp=sum[c1];
            ll c2=lower_bound(sum+i,sum+i+N-1,x+tp)-sum;
            if(!c2 || c2>i+N-1 || sum[c2]<x+tp) continue;tp=sum[c2];
            ll c3=lower_bound(sum+i,sum+i+N-1,x+tp)-sum;
            if(!c3 || c3>i+N-1 || sum[c3]<x+tp) continue;
            return 1;
        }
        return 0;
    }
     
    int main(){
        N=read(),L=0,R=0,ans=0;
        for(ll i=1;i<=N;i++)
            A[i]=read(),A[i+N]=A[i];
        for(ll i=1;i<=(N<<1);i++)
            sum[i]=sum[i-1]+A[i];
        R=sum[N];
        while(L<=R){
            ll mid=(L+R)>>1;
            if(check(mid)) ans=mid,L=mid+1;
            else R=mid-1;
        }
        printf("%lld
    ",ans);
        return 0;
    }
    /*
    61
    30
    62
    1 34 44 13 30 1 9 3 7 7 20 12 2 44 6 9 44 31 17 20 33 18 48 23 19 31 24 50 43 15
    63
    */

    $T2$(Loj2071):

    一棵$N+1$个点的有根树,每个点有贡献$P_i$和消耗$S_i$,在树上选择$K$个点使得$$frac {sum_{i=1}^{K} {P_i}} {sum_{i=1}^{K} {S_i}}$$最大,其中选择这个点的前提条件是它的父亲必须被选,根节点默认被选,其贡献和消耗均为0。

    ${N,Kleq2500,P_i,S_ileq10^4}$。

    $Solution$:

    既然是树上选点问题,我们可以考虑树形$dp$。但我们发现直接维护比值可能不满足最优性。

    比如$(20,1),(10000,1000)$在$P_i$和$S_i$很小的时候一定是选第二个更优的。

    但如果我们已知这个比值是$k$,问题就转化成是否有$$frac {sum_{i=1}^{K} {P_i}} {sum_{i=1}^{K} {S_i*k}}geq0$$的选择方案存在。此时我们可以使用树上背包判断比值$k$是否可以成为答案。

    那么若存在$k$可以成为答案,则$[0,k)$间所有比值均可成为答案。

    反之,若$k$不能成为答案,则$(k,+∞]$间所有比值都不可能成为答案。

    显然$k$满足单调性,我们可以二分答案然后$check$。

    这种方法有一个专有名词叫做“分数规划”。

    傻逼型树上背包$check$的复杂度为$O(N*K*K)$,我们考虑一些优化。

    可以发现转移顺序是原树的$dfs$序,$dfs$序的感性描述大概是:

    根在第一个,然后每棵子树按序排列,在每棵子树中,根在第一个,该子树的每棵子树按序排列……

    那么在$dfs$序的序列上,显然这个点如果不取可以直接跳过以它为根的一整棵子树,

    设该子树大小为$size(i)$则有$dp(i+size_i,j)=max{dp(i,j)}$。

    如果取,原来是按照$dfs$序转移的,现在也只需要将这个点在$dfs$序列中的后一个点更新即可。

    则有$dp(i+1,j+1)=max{dp(i,j)+P_i-k*S_i}$

    注意上面的$dp(i.j)$表示现在需要考虑第$i$个点的取舍情况,前$i-1$个点已经取完并且取了$j$个。

    总复杂度$O(N imes K imes log(max{P_i}))$,显然正确。

     

    $Code$:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    
    using namespace std;
    #define MAXN 2505
    #define MAXM 2505
    #define INF 0x7fffffff
    #define ll long long
    #define eps 1e-5
    
    double P[MAXN],S[MAXN],dp[MAXN][MAXM];int N,K,num;
    int hd[MAXN],nxt[MAXN],to[MAXN],dfn[MAXN],size[MAXN],cnt;
    inline int read(){
        int x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    inline void addedge(int u,int v){
        to[++cnt]=v,nxt[cnt]=hd[u];
        hd[u]=cnt;return;
    }
    
    inline void dfs(int u){
        dfn[num++]=u;size[u]=1;
        for(int i=hd[u];i;i=nxt[i])
            dfs(to[i]),size[u]+=size[to[i]];
        return;
    }
    
    inline bool check(double x){
        //cout<<x<<" ok"<<endl;
        for(int i=1;i<=N+1;i++)
            for(int j=0;j<=K+1;j++)
                dp[i][j]=-INF;
        for(int i=0;i<=N;i++)
            for(int j=0;j<=min(i,K+1);j++){
                if(dp[i][j]==-INF)continue;
                //if(x-0.001<0.001)cout<<P[dfn[i]]<<" "<<S[dfn[i]]<<endl;; 
                dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+P[dfn[i]]-x*S[dfn[i]]);
                dp[i+size[dfn[i]]][j]=max(dp[i+size[dfn[i]]][j],dp[i][j]);
                //if(x-0.001<0.001)cout<<i<<" "<<j<<" "<<dp[i][j]<<endl; 
            }
        return dp[N+1][K+1]>=eps;
    }
    
    int main(){
        K=read(),N=read();
        for(int i=1;i<=N;i++){
            cin>>S[i]>>P[i];
            int x=read();
            addedge(x,i);
        }
        dfs(0);//for(double i=1;i<=num;i++) cout<<dfn[i]<<" ";
        //cout<<endl;
        double l=0.0,r=10000.0;
        while(l+eps<r){
            double mid=(l+r)/2.0;
            if(check(mid)) l=mid;
            else r=mid; 
        }
        printf("%.3lf
    ",l);
        return 0;
    }

    $T3$(Loj2244):

    有N个数$A_i$和运算$B_i$,运算只可能是$or,xor,and$中的一种,

    现在你需要在${0...M}$间的所有正整数中选出一个数,使其经过$N$次运算后得到的值最大,求这个最大值。

    $Nleq10^5,Mleq2^{30},A_ileq2^{30}$。

    $Solution$:

    显然每一位的运算是互相独立的,我们可以分开处理。

    考虑从高位到低位贪心,该位运算后的答案能取1则尽量取1的策略,由于$2^{i}>2^{0}+2^{1}+...+2^{i-1}$所以该策略正确。

    既然这个二进制位在初始数中要么是0,要么是1,那我们只需要将该位初始置为0和1然后进行$N$次运算。

    • 若置为0时运算后答案为1,则该位一定为0。(既有贡献又不影响对原数大小)
    • 否则若置为1时运算后答案也为0,则该位答案无论如何也为0,该位一定为0。
    • 若置为1时运算后答案为1,那么若当前的原数加上这一位的1不超限则一定取1(参见上方贪心策略的证明),若超限则没法要了,取0。

    注意贪心时枚举位数要从30枚举而不是从$logM$枚举,因为高位是0也有可能答案为1。

    这种题为什么会是$T3$……

    $Code$:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    
    using namespace std;
    #define MAXN 100005
    #define MAXM 500005
    #define INF 0x7ffffff
    #define ll long long
    
    ll A[MAXN],B[MAXN];char str[MAXN];
    inline ll read(){
        ll x=0,f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar())
            if(c=='-')
                f=-1;
        for(;isdigit(c);c=getchar())
            x=x*10+c-'0';
        return x*f;
    }
    
    int main(){
        ll N=read(),M=read();
        for(ll i=1;i<=N;i++){
            cin>>str;A[i]=read();
            if(str[0]=='A') B[i]=1;
            if(str[0]=='O') B[i]=2;
            if(str[0]=='X') B[i]=3;
        }
        ll now=0,ans=0;
        for(ll pos=30;pos>=0;pos--){
            ll x1=0,x2=(1<<pos);
            for(ll i=1;i<=N;i++){
                if(B[i]==1) x1&=A[i],x2&=A[i];
                if(B[i]==2) x1|=A[i],x2|=A[i];
                if(B[i]==3) x1^=A[i],x2^=A[i];
            }
            if(x1&(1<<pos)) ans+=(1<<pos);
            else if(x2&(1<<pos) && now+(1<<pos)<=M) 
                ans+=(1<<pos),now+=(1<<pos);  
        }
        printf("%lld
    ",ans);
        return 0;
    }
  • 相关阅读:
    实验11——指针的基础应用
    C语言程序设计第10堂作业
    实验九——基本数据类型存储及应用总结
    C语言程序设计第8堂作业
    实验七——函数定义及调用总结
    实验六——循环结构程序练习总结
    实验五——循环结构学习总结
    实验四——多分支结构及本章总结
    9.29第三次作业
    zuoyeQAQ
  • 原文地址:https://www.cnblogs.com/YSFAC/p/9750579.html
Copyright © 2020-2023  润新知