题面
题意
有 m m m条横线, n n n条竖线,共有 ( n ∗ m ) (n*m) (n∗m)个交叉点, k ( k > = m + n ) k(k>=m+n) k(k>=m+n)个名字(每个名字是一个大写字母),现在要给这 ( m + n ) (m+n) (m+n)条线命名,若构成一个交叉点的两条直线的名字相同,则这个交叉点不美丽,问不美丽的交叉点的最少个数是多少。
做法
首先任意一条横线与任意一条竖线都会有一个交点,因此答案仅与哪些名字给了横线,哪些名字给了竖线有关,与每个名字具体给了哪条线无关。
而且只有当一种名字同时给了横线和竖线后才会产生不美丽的交叉点,个数为两边数量之积。
不难发现最优解中只有一种名字同时给了横线和竖线。
因此,我们可以暴力枚举那种既命名竖线又命名横线的名字
t
t
t,为了让这种名字出现的次数尽可能少,应尽可能多的用其他名字命名,所以其他同种名字必须全部命名横线或全部命名竖线(除非名字数量过多),因此可以01背包来求是否可以选择一些种类的名字,使其总个数为
i
(
i
<
=
n
)
i(i<=n)
i(i<=n),然后用这
i
i
i个名字用于命名横线,除了
t
t
t之外的
min
(
k
−
c
n
t
[
t
]
−
i
,
m
)
\min(k-cnt[t]-i,m)
min(k−cnt[t]−i,m)个名字命名竖线。
据此,我们可以暴力枚举名字
t
t
t用于命名横线的个数
i
i
i,则这种名字用于命名竖线的个数为
max
(
c
n
t
[
t
]
−
i
−
(
k
−
m
−
n
)
,
0
)
\max(cnt[t]-i-(k-m-n),0)
max(cnt[t]−i−(k−m−n),0),
a
n
s
=
min
(
a
n
s
,
i
∗
max
(
c
n
t
[
t
]
−
i
−
(
k
−
m
−
n
)
,
0
)
)
ans=\min(ans,i*\max(cnt[t]-i-(k-m-n),0))
ans=min(ans,i∗max(cnt[t]−i−(k−m−n),0)),这种情况合法当且仅当:用其他名字可以表示出
(
m
−
i
)
(m-i)
(m−i)个名字(同种名字必须同时使用,用01背包判断)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N 200100
using namespace std;
ll T,m,n,K,dy,cnt[30],ans;
char str[N];
bool dp[N]={1};
int main()
{
ll i,j,k;
cin>>T;
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) swap(m,n);
for(i=1;i<=K;i++) 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));
}
}
printf("%lld\n",ans);
}
}