校内集训20181001

$T1$(Loj2758):

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

$N\leq10^5,A_i\leq10^9$。

 

$Solution$:

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

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

在有序表中查找最小的大于等于某数的位置可以直接$lowerbound$,复杂度$O(N\times logN\times 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\n",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,K\leq2500,P_i,S_i\leq10^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\times K\times 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\n",l);
    return 0;
}

 

 

$T3$(Loj2244):

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

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

$N\leq10^5,M\leq2^{30},A_i\leq2^{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\n",ans);
    return 0;
}

 

posted @ 2018-10-08 14:33  Fugtemypt  阅读(202)  评论(0编辑  收藏  举报