AtCoder Beginner Contest 375 E - 3 Team Division

时间:2024-10-20 15:22:08

AtCoder Beginner Contest 375 E - 3 Team Division

在这里插入图片描述

题目大意

n n n个数字,初始时将它们分成了3组
现在可以进行一个操作:将一个数从某一组换到另外一组
问最后能否使三组数组内数字之和相等,若不能则输出 − 1 -1 1,反之则输出最少操作次数

思路

真是一道阳间的DP啊啊啊啊
首先若是 ∑ i = 1 n a i \sum_{i=1}^{n}a_i i=1nai 不是n的倍数,显然无法操作成功
然后…我卡了半天没有想法赛时直接没交
于是我们不妨从数据范围入手,注意到

n < = 100 , ∑ b i < = 1500 n<=100,\sum b_i<=1500 n<=100bi<=1500

属于那种搜索一定会T,正解是奇形怪状的DP的抽象题
我们不妨设 f [ i ] [ j ] [ k ] [ x ] f[i][j][k][x] f[i][j][k][x] 表示处理了前 i i i个数,第1组总和是 j j j,第2组总和是 k k k,第3组总和是 x x x 所需要操作的最小次数,然后必然是MLE了
注意到前两组数的总和一旦确定,第三组数的总和必然是一定的,因此最后一维可以直接扔掉,类比背包计数问题,我们可以得出这样一个状态转移方程
假设当前处理的第 i i i 个数属于第一组,我们有
f [ i ] [ j ] [ k ] = m i n ( f [ i ] [ [ j ] [ k ] [ , f [ i − 1 ] [ j − b [ i ] ] [ k ] ) f[i][j][k]=min(f[i][[j][k][,f[i-1][j-b[i]][k]) f[i][j][k]=min(f[i][[j][k][,f[i1][jb[i]][k])
直接填入第一组,不消耗操作次数

f [ i ] [ j ] [ k ] = m i n ( f [ i ] [ [ j ] [ k ] [ , f [ i − 1 ] [ j ] [ k − b [ i ] ] + 1 ) f[i][j][k]=min(f[i][[j][k][,f[i-1][j][k-b[i]]+1) f[i][j][k]=min(f[i][[j][k][,f[i1][j][kb[i]]+1)
从第一组切换到第二组

f [ i ] [ j ] [ k ] = m i n ( f [ i ] [ [ j ] [ k ] [ , f [ i − 1 ] [ j ] [ k ] + 1 ) f[i][j][k]=min(f[i][[j][k][,f[i-1][j][k]+1) f[i][j][k]=min(f[i][[j][k][,f[i1][j][k]+1)
从第一组切换到第三组

后续 a [ i ] = 2 , 3 a[i]=2,3 a[i]=2,3 同理可得
最后答案是 f [ n ] [ s u m / 3 ] [ s u m / 3 ] f[n][sum/3][sum/3] f[n][sum/3][sum/3]

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#define rep(i,x,y) for(ll i=x;i<=y;++i)
#define per(i,x,y) for(ll i=x;i>=y;--i)
using namespace std;
typedef long long ll;
const ll V=520,inf=1e18;
ll n,a[V],c[V];
ll sum,f[V][V][V];//f[i][j][k] 前i个人 1 j 2 k 3 sum-j -k 的最少次数(压去一维) 
inline ll in()
{
    ll res=0,f=1;
    char ch;
    while((ch=getchar())<'0'||ch>'9')
     if(ch=='-') f=-1;
    res=res*10+ch-'0';
    while((ch=getchar())>='0'&&ch<='9')
     res=res*10+ch-'0';
    return res*f;
}
inline void put(ll x)
{
    if(x<0) putchar('-'),x*=-1;
    if(x>9) put(x/10);
    putchar(x%10+48);
}
int main()
{
	n=in();
	rep(i,1,n)
	{
		c[i]=in(),a[i]=in();
		sum+=a[i];
	}
	if(sum%3)
	{
		printf("-1");
		return 0;
	}
	rep(i,0,n)
	 rep(j,0,500)
	  rep(k,0,500)
	   f[i][j][k]=inf; 
	f[0][0][0]=0;
	rep(i,1,n)
	{
		switch(c[i])
		{
			case(1) :
			{
				per(j,500,a[i])
				 per(k,500,0)
				  f[i][j][k]=min(f[i][j][k],f[i-1][j-a[i]][k]);//直接把b[i]填进来
				per(j,500,0)
				 per(k,500,a[i])
				  f[i][j][k]=min(f[i][j][k],f[i-1][j][k-a[i]]+1);//b[i]切换到2 
				per(j,500,0)
				 per(k,500,0)
				  f[i][j][k]=min(f[i][j][k],f[i-1][j][k]+1);//b[i]切换到3 
				break ;
			}//后续同理 
			case(2) :
			{
				per(j,500,0)
				 per(k,500,a[i])
				  f[i][j][k]=min(f[i][j][k],f[i-1][j][k-a[i]]);//直接把b[i]填进来
				per(j,500,a[i])
				 per(k,500,0)
				  f[i][j][k]=min(f[i][j][k],f[i-1][j-a[i]][k]+1);//b[i]切换到1 
				per(j,500,0)
				 per(k,500,0)
				  f[i][j][k]=min(f[i][j][k],f[i-1][j][k]+1);//b[i]切换到3 
				break ;
			}
			default :
			{
				per(j,500,0)
				 per(k,500,0)
				  f[i][j][k]=min(f[i][j][k],f[i-1][j][k]);//直接把b[i]填进来
				per(j,500,a[i])
				 per(k,500,0)
				  f[i][j][k]=min(f[i][j][k],f[i-1][j-a[i]][k]+1); //b[i]切换到1 
				per(j,500,0)
				 per(k,500,a[i])
				  f[i][j][k]=min(f[i][j][k],f[i-1][j][k-a[i]]+1);//b[i]切换到1 
				break ; 
			}
		}
	}
	if(f[n][sum/3][sum/3]==inf) printf("-1");
	else printf("%lld",f[n][sum/3][sum/3]);
    return 0;
}