BZOJ1181 IZBROI选举【二分+DP】

题目描述:

共有V张票,每一票只可能投给N个政党中的一个。共有M个席位。设编号为i的政党最终的得票为Vi,则议会中的席位按如下规则分配:
1、将得票数小于总选票的5%的政党剔除。
2、初始时议会为空,每个政党都只有0个席位。
3、对于每个政党P,计算一个参数Qp = Vp / (Sp + 1),Vp为政党P的最终得票,Sp为政党P当前已经在议会拥有的席位。
4、给Qp最大的政党分配一个席位,如果有多个政党的Qp相同,则将席位分给其中编号最小的政党。
5、重复3和4,直到议会已满。
由于计票还没有结束,现在我们只知道一部分选票的投票结果。给出V、N、M以及每个政党当前的得票,请你计算每个政党最多以及最少能赢得多少个席位。

N<=100,M<=200

题目分析:

经过一番思考之后觉得这个Qp飘忽不定不可捉摸不知所措然后被水淹没。

最大值很好解决,枚举每个人,把剩的所有票都给它然后暴力模拟,O(n2m)

最小值的话,二分当前 x x x号政党的席位数 m i d mid mid,然后求出剩下的政党得到 ≥ m − m i d \ge m-mid mmid个席位需要的最小票数,用 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个政党得到 ≥ j \ge j j个席位需要的最小票数,有 f [ i ] [ j ] = f [ i − 1 ] [ j − k ] + c o s t ( i , k ) f[i][j]=f[i-1][j-k]+cost(i,k) f[i][j]=f[i1][jk]+cost(i,k)

c o s t ( i , k ) cost(i,k) cost(i,k)表示让第 i i i个政党夺走 k k k个席位的最小需票数。
需要满足

  • a [ x ] m i d + 1 < a [ i ] + c o s t ( i , k ) k + [ i < x ] \frac {a[x]}{mid+1}<\frac {a[i]+cost(i,k)}{k}+[i<x] mid+1a[x]<ka[i]+cost(i,k)+[i<x]
  • a [ i ] + c o s t ( i , k ) ≥ 5 % V a[i]+cost(i,k)\ge5\%V a[i]+cost(i,k)5%V

为什么这么做是对的?
实际上第 i i i个政党最后不一定得到了 k k k个席位,但是可以确定的是,在 x x x政党将要取得mid+1个席位之时, i i i政党一定取得到 k k k个席位。相当于用最小的票数构成一个确定的限制。并且在这个限制总数<m-mid的时候, x x x政党一定可以取到第mid+1个席位。所以DP的正确性得以保证。

感性理解一波吧,这个题我想了2h+。。。

Code:

#include<bits/stdc++.h>
#define maxn 105
#define maxm 205
using namespace std;
int n,m,V,a[maxn],b[maxn],sum,f[maxm],ans[maxn],id[maxn];
void Maxsolve(){
	for(int k=1;k<=n;k++){
		a[k]+=V-sum,memset(b,0,(n+1)<<2);
		for(int t=1,p;t<=m;b[p]++,t++){
			for(p=1;p<=n;p++) if(a[p]*20>=V) break;
			for(int i=p+1;i<=n;i++) if(a[i]*20>=V&&a[i]*(b[p]+1)>a[p]*(b[i]+1)) p=i;
		}
		a[k]-=V-sum,printf("%d%c",b[k],k==n?10:32);
	}
}
bool cmp(int i,int j){return a[i]>a[j];}
bool cmp2(int i,int j){return i>j;}
bool check(int x,int mid){
	memset(f,0x3f,sizeof f),f[0]=0;
	for(int i=1;i<=n&&i<=20;i++) if(i!=x){
		for(int j=m-mid;j>=1;j--)
			for(int k=1;k<=j;k++){
				int cst=max((a[x]*k%(mid+1)?a[x]*k/(mid+1)+1:a[x]*k/(mid+1)+(id[i]>id[x]))-a[i],0);
				if((a[i]+cst)*20<V) cst=(V-1)/20+1-a[i];
				f[j]=min(f[j],f[j-k]+cst);
			}
	}
	return f[m-mid]<=V-sum;
}
int main()
{
	scanf("%d%d%d",&V,&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
	Maxsolve();
	for(int i=1;i<=n;i++) id[i]=i;
	sort(id+1,id+1+n,cmp),sort(a+1,a+1+n,cmp2);
	for(int i=1;i<=n;i++) if(a[i]*20>=V){
		int l=0,r=m,mid;
		while(l<r){
			mid=(l+r)>>1;
			if(check(i,mid)) r=mid;
			else l=mid+1;
		}
		ans[id[i]]=l;
	}
	for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?10:32);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值