【BZOJ1181】[CROATIAN2009] IZBROI选举(贪心+模拟&&二分+DP)

点此看题面

大致题意: 共有(V)张选票,有(n)个政党竞争(m)个席位。设第(i)个政党有(V_i)张选票,已获得(S_i)个席位,则每个席位将会分给获得选票数量大于等于总选票数(5\%)的政党中此时(frac{V_i}{S_i+1})最大(相等时(i)最小)的政党。已知每个政党当前已经获得(a_i)张选票,问每个政党最多及最少可能获得多少个席位。

前言

今天晚上状态不行,这样一道题目浑浑噩噩地写了半个多小时。

本来想写道数据结构题的,现在看来幸好没有写,否则按照现在的状态不知道要调到哪年。

最多

显然贪心+模拟即可,把剩余的所有票都给当前政党,然后模拟分席位的过程求出最大的席位数。

最少

考虑二分答案(t),然后再(DP)验证。

由于选票数量要大于等于(5\%),因此实际上最多只有(20)个政党能得到选票。

(f_{i,j})表示前(i)个政党获得(j)个席位需要的最少额外选票数,每次枚举(k)转移,要满足两个条件:

  • (frac{V_i}{k})大于(或大于等于)(frac{a_x}{t+1})
  • (V_ige5\% imes V)

那么只要求出满足条件的最小的(V_i),再减去已有的(a_i),就是当前情况下所需最少选票数。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define M 200
#define INF 1e9
using namespace std;
int tot,p,n,m,a[N+5],s[N+5];
I int BF_for_Mx(CI x)//贪心+模拟
{
	RI i,j,t;for(i=1;i<=n;++i) s[i]=0;for(a[x]+=p,i=1;i<=m;++s[t],++i)
		for(t=0,j=1;j<=n;++j) 20*a[j]>=tot&&(!t||a[j]*(s[t]+1)>a[t]*(s[j]+1))&&(t=j);
	return a[x]-=p,s[x];
}
int g[N+5],f[N+5][M+5];I bool cmp(CI x,CI y) {return a[x]>a[y];}
I bool Check(CI x,CI t)//DP验证
{
	RI i,j,k;for(i=1;i<=min(n,20);++i) for(j=0;j<=m-t;++j)
		if(f[i][j]=f[i-1][j],g[i]^x) for(k=1;k<=j;++k) f[i][j]=min(f[i][j],
			f[i-1][j-k]+max(max((a[x]*k-(g[i]<x))/(t+1)+1,(tot-1)/20+1)-a[g[i]],0));//注意满足多重限制
	return f[min(n,20)][m-t]<=p;
}
I int EF_for_Mn(CI x)//二分
{
	if(20*a[x]<tot) return 0;RI l=0,r=m,mid;
	W(l<r) Check(x,mid=l+r-1>>1)?r=mid:l=mid+1;return r;//二分答案
}
int main()
{
	RI i;for(scanf("%d%d%d",&tot,&n,&m),p=tot,i=1;i<=n;++i) scanf("%d",a+i),p-=a[i];//p记录剩余选票数
	for(i=1;i<=n;++i) printf("%d%c",BF_for_Mx(i)," 
"[i==n]);
	for(i=1;i<=n;++i) g[i]=i;sort(g+1,g+n+1,cmp);for(i=1;i<=m;++i) f[0][i]=INF;//初始化
	for(i=1;i<=n;++i) printf("%d%c",EF_for_Mn(i)," 
"[i==n]);return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ1181.html