• BZOJ 1181: [CROATIAN2009] IZBROI选举(二分+dp)


    题面

    在一个地区的选举中,共有V个人参加了投票,每一票只可能投给N个政党中的一个。当地的议会共有M个席位。不妨将N个政党编号为1到N,并且设编号为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以及每个政党当前的得票,请你计算每个政党最多以及最少能赢得多少个席位。

    题解

    记剩下所有票数为sumsum

    xx位置上的的最大值就是把sumsum全部给xx,然后暴力枚举就行了。每次时间复杂度O(n2)O(n^2),总时间复杂度O(n3)O(n^3)

    下面是求xx的最小值。方法是二分,假设二分的值是midmid,问题就转化为是否能让剩下的人凑足mmidm-mid个席位。可以发现只有票数5%geq 5\%的人才可能获得席位。所以只有票数前2020才可能获得席位。按贪心的思路,一定是从大到小排序后去前2020个来凑足mmidm-mid个席位。

    f[i][j]f[i][j]表示用前ii个占领jj个席位所需要的最少额外票数,那么转移为:
    f[i][j]=mink=0jf[i1][jk]+cost(i,k)f[i][j]=min_{k=0}^jf[i-1][j-k]+cost(i,k)cost(i,k)cost(i,k)表示让第ii个位置的人占领kk个席位所需要的最小票数。

    要在xx占领midmid个席位的情况下,让第ii个位置的人占领kk个席位,需要满足的条件有:

    • (a[x]mid+1<a[i]k)(frac{a[x]}{mid+1}< frac{a[i]}{k}) 或者 (a[x]mid+1=a[i]k&& i<x)(frac{a[x]}{mid+1}= frac{a[i]}{k}&& i的编号<x的编号)
    • a[i]+cost(i,k)V5%a[i]+cost(i,k)geq V*5\%

    那么直接转移就行了。每次时间复杂度O(20m2)O(20*m^2),总时间复杂度O(20m2logm)O(20*m^2log m)

    CODE

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    inline void rd(int &x) {
    	char ch; int flg=1; while(!isdigit(ch=getchar()))if(ch=='-')flg=-flg;
    	x = 0; do x=x*10+ch-'0'; while(isdigit(ch=getchar())); x*=flg;
    }
    const int MAXN = 205;
    int n, m, V, sum, a[MAXN], c[MAXN], id[MAXN];
    inline bool cmp(int i, int j) { return a[i] > a[j]; }
    int ans[MAXN], s[MAXN];
    inline void Solve_mx() { //暴力枚举模拟
    	for(int i = 1; i <= n; ++i) {
    		a[i] += sum;
    		for(int j = 1; j <= n; ++j) s[j] = 0;
    		for(int j = 1; j <= m; ++j) {
    			int x = -1;
    			for(int k = 1; k <= n; ++k) if(a[k]*20 >= V) {
    				if(x == -1 || a[k]*(s[x]+1) > a[x]*(s[k]+1)) x = k;
    			}
    			if(~x) ++s[x];
    		}
    		printf("%d%c", s[i], " 
    "[i==n]);
    		a[i] -= sum;
    	}
    }
    int f[21][MAXN];
    inline bool check(int x, int mid) {
    	int cur = 0; for(int i = 0; i <= m; ++i) f[cur][i] = sum+1;
    	f[cur][0] = 0;
    	for(int i = 1; i <= n && i <= 20; ++i) {
    		if(i == x) continue; cur ^= 1;
    		for(int j = 0; j <= m; ++j) {
    			f[cur][j] = sum+1;
    			for(int k = 0; k <= j; ++k) {
    				int delta = (k * a[x] + mid) / (mid + 1) - a[i]; //向上取整
    				if(k * a[x] % (mid + 1) == 0 && id[x] < id[i] && k) ++delta; //判断在原数列编号大小
    				delta = delta < 0 ? 0 : delta;
    				if(k && (a[i]+delta)*20 < V) delta += (V - (a[i]+delta)*20 + 19) / 20; //凑足5%
    				f[cur][j] = min(f[cur][j], f[cur^1][j-k] + delta);
    			}
    		}
    	}
    	return f[cur][m-mid] <= sum;
    }
    inline int getmin(int x) {
    	if(a[x]*20 < V) return 0;
    	int l = 0, r = m, mid;
    	while(l < r) {
    		mid = (l + r) >> 1;
    		if(check(x, mid)) r = mid;
    		else l = mid+1;
    	}
    	return l;
    }
    
    inline void Solve_mn() {
    	for(int i = 1; i <= n; ++i) id[i] = i, c[i] = a[i];
    	sort(id + 1, id + n + 1, cmp); //从大到小排序
    	for(int i = 1; i <= n; ++i) a[i] = c[id[i]];
    	for(int i = 1; i <= n; ++i) ans[id[i]] = getmin(i);
    	for(int i = 1; i <= n; ++i) printf("%d%c", ans[i], " 
    "[i==n]);
    }
    int main () {
    	rd(V), rd(n), rd(m), sum = V;
    	for(int i = 1; i <= n; ++i) rd(a[i]), sum -= a[i];
    	Solve_mx();
    	Solve_mn();
    }
    
  • 相关阅读:
    三列布局_左右绝对定位_中间适应
    三列布局_左右固定_中间自适应
    两列布局_左右二侧_绝对定位
    二列布局_左右固定_自己撑开父级块
    两列布局_右侧固定_左侧自适应
    两列布局_左侧固定_右侧自适应
    单列布局_宽度自适应_内容居中
    单列布局_上中下等宽
    聊一聊Unity协程背后的实现原理
    发火箭和做游戏有什么共通点?
  • 原文地址:https://www.cnblogs.com/Orz-IE/p/12039209.html
Copyright © 2020-2023  润新知