CF1070J Streets and Avenues in Berhattan 题解

本文探讨了一种算法,旨在解决给定横线、竖线和名称约束下,如何分配名称以最小化由同名直线构成的交叉点数量的问题。通过优化颜色分配策略,避免在横线和竖线上出现重复名称,从而减少不美丽交叉点的产生。

1 题目大意

mmm条横线,nnn条竖线,共有 (n∗m)(n*m)(nm) 个交叉点.
(m+n≤k)(m+n≤k)(m+nk)个名字(每个名字是一个大写字母),现在要给这(m+n)(m+n)(m+n)条线命名,若构成一个交叉点的两条直线的名字相同,则这个交叉点不美丽。

问不美丽的交叉点的最少个数是多少。

多组数据,第一行t(数据组数)(1≤t≤30000)(1≤t≤30000)(1t30000)

每组数据依次为n,m,kn,m,kn,m,k,第二行是名称.

(1≤n,m≤30000;n+m≤k≤2∗105)(1≤n,m≤30000;n+m≤k≤2*10^5)(1n,m30000;n+mk2105)

2 分析

我们对于一种名称,要尽可能将它全部置于横线或者全部置于竖线上,对于样例1的第二个数据来说,最优方案就是把2条竖线全部置为E,横线中两个E,五个Z。这样看来,我们只需要关注名称分配到横线上还是竖线上了,不必考虑分配到哪个线上。

我们的目标就变成分配颜色到横线和竖线中(而不是哪根横线和哪根竖线中),使得横竖同名数尽量少(因为ansansans=横向同名*纵向同名)。

再举一个例子:

EF
E+
E+
E+
E+
E+
E+
F+

这样填空肯定不是最优的,因为有两种名字都重复了,我们可以将行中的FFF和列中的EEE互换,那么就可以达到更优情况。所以我们又有了一个结论:当重名种类大于等于222时,一定不是最优解。

那么接下来就很清晰了,我们枚举复的名字种类,去看哪种重复更好。然后我们考虑iii为重复的颜色,jjj为它在列中存在的个数,那么它在行中会有(cnt[i]−j−(k−n−m)(cnt[i]-j-(k-n-m)(cnt[i]j(knm)个(cnt[i]cnt[i]cnt[i]表示字母iii的个数)。

ans=cnt[i]−j−(k−n−m)ans=cnt[i]-j-(k-n-m)ans=cnt[i]j(knm)

这个结论成立,当且仅当其他的字母可以填满列中剩余的(n−j)(n-j)(nj)个空。

我们只需要在更新ansansans之前判断其他的字母可以表示的位置就行了。

3 代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N 200100
#define maxx 27
using namespace std;
ll T,m,n,K,dy,cnt[maxx],ans;
char str[N];
bool dp[N];
int main(){
    ll i,j,k;
    scanf("%lld",&T);
    dp[0]=1;
    while(T--){
        memset(cnt,0,sizeof cnt);
        ans=INF;
        scanf("%lld%lld%lld%s",&m,&n,&K,&str[1]);
        dy=K-m-n;
        if(m>n){//保证n是大的
            swap(m,n);
        }
        for(i=1;i<=K;i++){//出理cnt数组
            cnt[str[i]-'A'+1]++;
        }
        for(i=1;i<=26;i++){
            for(j=1;j<=n;j++){//清空
                dp[j]=0;
            }
            for(j=1;j<=26;j++){
                if(i==j){//要是不同种颜色
                    continue;
                }
                for(k=n;k>=cnt[j];k--){//处理能填满的
                    dp[k]|=dp[k-cnt[j]];
                }
            }
            for(j=min(cnt[i],n);j>=0;j--){
                if(!dp[n-j]){//如果其他颜色不能填到这
                    continue;
                }
                ans=min(ans,j*max(0ll,cnt[i]-j-dy));//更新ans
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值