20170703练习赛比赛总结

C

思路
  • 我们发现对于max直接暴力即可
  • 可是对于最小值就没有办法了
  • 我们可以二分党派获得的票数
  • 可是,由于有5%的限制,直接二分不可行
  • 此时我们可以思考一下终态:
    • 一定会有一定数量 (c) 的党派有一定 (x) 数量席位
  • 想到这里,我们可以通过枚举 c 再二分 x
/*
    思路:对于最大值,我们只需要把多的票都给它即可             
          对于最小值,我们可以先枚举有多少个党派可以有席位之后二分最小席位即可 
          在二分时,我们直接先把x强行填成mid票
          之后再每一次选举时,如果x要获得座位时我们都找到一个最小票数投给一个党,把票给他 
*/ 
#include<bits/stdc++.h>
using namespace std;
const int M=105;
struct node{
    int x,s,i;
    bool operator<(const node&A)const{
        if(x*A.s!=A.x*s)return x*A.s<A.x*s;
        return i>A.i;
    }
}A[M],B[M],C[M];
int n,m,V,res,mi[M],t,Q[M],limt;
void MAX(){
    for(int x=1;x<=n;x++){
        for(int i=1;i<=n;i++)B[i]=A[i];
        B[x].x+=res;
        for(int c=1;c<=m;c++){
            int k=0;
            for(int i=1;i<=n;i++){
                if(B[i].x<limt)continue;
                if(B[k]<B[i]||(!k))k=i;
            }B[k].s++;
        }
        printf("%d ",B[x].s-1);
    }puts("");
}
bool check(int h,int x,int RRR){
    /*init*/ 
    for(int i=1;i<=n;i++)C[i]=B[i];
    C[x].s=h+1;
    int cas=m-h;
    /*模拟选举*/ 
    while(cas--){
        int f=0,T=0,ned=0;
        for(int i=1,y;i<=t;i++){
            y=Q[i];
            /*找到了较优的人了*/ 
            if(C[x]<C[y]){f=1;C[y].s++;break;}
            /*找cost小的人了*/ 
            if(C[x].i<C[y].i){
                int use=(C[x].x*C[y].s)/C[x].s-C[y].x+1;
                if((!T)||ned>use)T=y,ned=use;
            }else{
                int use=(C[x].x*C[y].s+C[x].s-1)/C[x].s-C[y].x;
                if((!T)||ned>use)T=y,ned=use;
            }
        }
        if(!f)RRR-=ned,C[T].s++,C[T].x+=ned;
        if(RRR<0)return 0;
    }return 1;
}
void solve(int x){
    if(A[x].x<limt)return;
    int &ans=mi[A[x].i];
    ans=m;
    /*枚举有几个党派获得席位*/
    for(int c=1;c<=n;c++){
        /*init*/
        for(int i=1;i<=n;i++)B[i]=A[i];
        int Res=res;t=0;
        /*我们取最优的几个让他们获得席位*/
        for(int i=n;i>=1&&t<=c;i--)if(i!=x){
            Q[++t]=i;
            if(B[i].x<limt)Res-=limt-B[i].x,B[i].x=limt;/*4个小时!!!!!!*/
        }
        if(Res<0)continue;
        int L=0,R=ans;
        while(L<=R){
            int mid=L+R>>1;
            if(check(mid,x,Res))ans=mid,R=mid-1;
            else L=mid+1;   
        }
    }
}
void MIN(){
    sort(A+1,A+1+n);
    for(int i=1;i<=n;i++)solve(i);
    for(int i=1;i<=n;i++)printf("%d ",mi[i]);
}
int main(){
    scanf("%d %d %d",&V,&n,&m);
    res=V;limt=(V+19)/20;
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);
        res-=x;
        A[i]=(node){x,1,i};
    }
    MAX(),MIN();  
    return 0;
}
小结
  • 对于该类问题,我们可以通过分析终态进行分析,不要拘泥于答案本身,需要挖掘最终答案的形成形式
  • 如果对于一些限制条件(也可以是二分)不知道怎么判断是否可行的时候,可以分析权值是怎么变化的以及它的变化因素是什么,从而更加清晰的分析问题。
    • 例如该题,我们发现直接判断 x 党派投k票很困难,我们就可以逆向的考虑,即如何把除了 k 票以外的票都给其他党派。于是我们直接把k票都先给 x 党(使他的权值最不优)。

D

思路

  • 很容易发现直接枚举x是不可能的
  • 此时我们要尝试去枚举一些容易枚举的东西
/*
    我们发现每个F(x)都可以表示成x*2^a[0]*3^a[1]*5^a[2]*7^a[3]
    因为a[]的组合并不多我们可以通过枚举a[]来计算
    此时我们可以套入记忆化搜索来实现加速   
*/
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int M=20;
int pri[]={2,3,5,7},a[4];
int fac[10][4]={
    {0,0,0,0},{0,0,0,0},{1,0,0,0},{0,1,0,0},{2,0,0,0},
    {0,0,1,0},{1,1,0,0},{0,0,0,1},{3,0,0,0},{0,2,0,0} 
};
ll A,B,L,R,Dec[M],dp[M][30][19][13][11];
ll DP(int c,ll num){
    ll mx=num+Dec[18-c]-1;
    if(mx<L||num>R)return 0;
    if(c==18)return !a[0]&&!a[1]&&!a[2]&&!a[3];
    bool hav=num>=L&&mx<=R;
    ll res=0,&ans=dp[c][a[0]][a[1]][a[2]][a[3]];
    /*如果L<=[mi,mx]<=R 即随便放都可以 于是我们用记忆化搜索*/ 
    if(hav&&~ans)return ans;
    for(int i=0<num;i<10;++i){
        bool f=1;
        For(j,0,3)if(a[j]<fac[i][j]){f=0;break;}
        if(!f)continue;
        For(j,0,3)a[j]-=fac[i][j];
        res+=DP(c+1,i*Dec[17-c]+num);
        For(j,0,3)a[j]+=fac[i][j];
    }
    /*否则不更新DP的答案*/ 
    if(hav)ans=res;
    return res;
}
ll pre(int c,ll pro){
    /*pro*pro可能炸掉然后小于0了*/ 
    if(pro*pro<0||pro*pro>B)return 0;
    if(c==4){
        L=(A+pro-1)/pro;R=B/pro;
        return DP(0,0);
    }
    ll res=pre(c+1,pro);
    a[c]++;
    res+=pre(c,pro*pri[c]);
    a[c]--;
    return res;
}
int main(){
    scanf("%lld%lld",&A,&B);
    Dec[0]=1;
    For(i,1,M-1)Dec[i]=Dec[i-1]*10;
    memset(dp,-1,sizeof(dp));
    printf("%lld\n",pre(0,1));
    return 0;
}
小结
  • 枚举信息时要明确枚举的信息再接上其他的算法
  • 量化枚举信息可以做到事半功倍的效果
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值