Codeforces 1267 E.Elections

题目大意

n n n名候选人, m m m个投票站,
每名候选人的得票总数为每个投票站得票数的和。
一名候选人能够当选,当且仅当他的总得票数严格大于其余的所有候选人。
现在已知每名候选人在每个投票站的得票情况,每个投票站的得票数不会超过 1 0 3 10^3 103
问至少要删掉多少个投票站才能不让第 n n n名候选人当选。
需要输出具体删掉的投票站,多种方案输入任意一种方案即可。

时间限制

3s

数据范围

n , m ≤ 100 n,m\le100 n,m100

题解

为了不让第 n n n位候选人当选,只需要前面的 n − 1 n-1 n1位中的任意一位的得票数超过他即可。
于是就可以枚举前面的哪一位候选人超过他,这样 n n n个人比较的问题就转化成了 2 2 2个人比较的问题,处理起来就会方便很多。
考虑如何设状态,
如果设 f i , j f_{i,j} fi,j表示前 i i i个投票站中,使得两名候选人的得票为 j j j所需要的最多投票站的数量。
看上去非常好转移,但是不难发现这个状态数实在太多了,不可能全部记下来,而且也会TLE。

不过可以看出来, f i , j f_{i,j} fi,j的数值很小,不会超过 m m m,于是不妨换一下,设状态 g i , j g_{i,j} gi,j表示前 i i i个投票站,选了 j j j个投票站,两名候选人的得票数差最大为多少。
转移也非常简单: g i , j = ∑ k = 0 i − 1 m a x ( g k , j − 1 + 选 第 i 个 投 票 站 产 生 的 得 票 数 差 值 , g i − 1 , j ) g_{i,j}=\displaystyle\sum_{k=0}^{i-1} max \pod{g_{k,j-1}+ 选第i个投票站产生的得票数差值 , g_{i-1,j}} gi,j=k=0i1max(gk,j1+i,gi1,j)
因为要输出方案,因此在转移的时候,需要记录一下每个状态是从哪一个状态转移过来的,在最后统计答案的时候还原回去。

Code

//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#define G getchar
#define ll long long
using namespace std;

int read()
{
    char ch;
    for(ch = G();(ch < '0' || ch > '9') && ch != '-';ch = G());
    int n = 0 , w;
    if (ch == '-')
    {
        w = -1;
        ch = G();
    } else w = 1;
    for(;'0' <= ch && ch <= '9';ch = G())n = (n<<1)+(n<<3)+ch-48;
    return n * w;
}

const int N = 103;

int n , m , a[N][N];
int f[N][N] , g[N][N];
int ans , z[N];
bool bz[N];

void work (int x)
{
    memset(f , 128 , sizeof(f));
    f[0][0] = 0;
    for (int i = 1 ; i <= m ; i++)
    {
        f[i][0] = 0;
        for (int j = 1 ; j <= i ; j ++)
            for (int k = j - 1 ; k < i ; k++)
            {
                if (f[k][j - 1] == f[0][1]) continue;
                if (f[i][j] < f[k][j - 1] + a[i][x] - a[i][n])
                {
                    f[i][j] = f[k][j - 1] + a[i][x] - a[i][n];
                    g[i][j] = k;
                }
            }
    }
}

int main()
{
    //freopen("c.in","r",stdin);
    //freopen("c.out","w",stdout);

    n = read();
    m = read();
    for (int i = 1 ; i <= m ; i++)
        for (int j = 1 ; j <= n ; j++)
            a[i][j] = read();

    ans = 0;
    for (int i = 1 ; i < n ; i++)
    {
        work(i);
        for (int k = m ; k ; k--)
            for (int j = k ; j > ans ; j--)
                if (f[k][j] >= 0)
                {
                    ans = j;
                    int pos = k;
                    for (int w = 1 ; w <= ans ; w++)
                    {
                        z[w] = pos;
                        pos = g[pos][j - w + 1];
                    } 
                    break;
                }
    }

    printf("%d\n", m - ans);
    memset(bz , 1 , sizeof(bz));
    for (int i = 1 ; i <= ans ; i++)
        bz[z[i]] = 0;

    for (int i = 1 ; i <= m ; i++)
        if (bz[i]) printf("%d ", i);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值