教主的别墅(villa)


第三题:教主的别墅(villa

(villa.pas/cpp/in/out)

villa.in

villa.out

【题目背景】

  LHX教主身为宇宙第一富翁,拥有一栋富丽堂皇的别墅,由于别墅实在太大了,于是教主雇佣了许许多多的人来负责别墅的卫生工作,我们不妨称这些人为LHXee。

 

【题目描述】

  教主一共雇佣了N个LHXee,这些LHXee有男有女。

  教主的大别墅一共有M个房间,现在所有的LHXee在教主面前排成了一排。教主要把NLHXee分成恰好M个部分,每个部分在队列中都是连续的一段,然后分别去打扫M个房间。

  教主身为全世界知识最渊博的人,他当然知道男女搭配干活不累的道理,以及狼多羊少,羊多狼少的危害之大。所以教主希望一个分配方式,使得所有小组男女个数差的最大值最小。

教主还希望你输出从左到右,每个组的人数。

  如果有多种人数组合都能达到最优值,教主希望你分别告诉他这些方案中字典序最小和最大的方案。换句话说,你需要找到两种方案,这两种方案满足所有组男女个数差最大值最小的前提下,第一种方案(字典序最小)要越靠前的组人数越少,也就是让第一个小组人尽量少,并且在第一个小组人尽量少的前提下,让第二个小组的人尽量少,依此类推;第二种方案(字典序最大)则要让越靠前的组人数越多。

 

【输入格式】

  输入的第1行为两个正整数N与M,用空格分隔。

  第2行包含一个长度为N的串,仅由字符组成,第i 个字符为0表示在这个位置上的LHXee为女生,若为1则为男生。

 

【输出格式】

输出文件包含两行,每行M个正整数,正整数之间用空格隔开,行末无多余空格。这M个正整数从左到右描述了你所分的每个组的人数。

第1行为字典序最小的方案,第2行为字典序最大的方案。

 

【样例输入】

8 3

11001100

 

【样例输出】

1 2 5

5 2 1

 

【样例说明】

字典序最小的方案按1, 10, 01100分组,每组男女个数差的最大值为1,为最小。

字典序最大的方案按11001, 10, 0分组。

 

【数据规模】

对于40%的数据,有N ≤ 100;

对于50%的数据,有N ≤ 1000;

对于65%的数据,有N ≤ 100000;

对于100%的数据,有N ≤ 5000000,M ≤ N且M ≤ 100000。

 

【提示】

关于字典序:

比较S1[N]与S2[N]的字典序大小,可以找到S1[N]与S2[N]中第1个不相同数字S1[i]与S2[i](即有对于所有1≤k<i,都有S1[k] =S2[k],但S1[i]≠S2[i])。如果S1[i]<S2[i],那么说S1[N]字典序比S2[N]小,否则说S1[N]字典序比S2[N]大。



第一眼:二分,转化判定性问题

于是就慢慢慢慢分,写个dfs分吧~~~

然后再看:求出答案

这tm怎么求啊!!!!

于是慢慢慢慢求,写两个dfs吧~~~

果断T掉,还好有个35分不错了


我的Code:

//#define _TEST _TEST
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
/
#define for0(i,j) for(int i=0;i<j;i++)
#define for1(i,j) for(int i=1;i<=j;i++)
#define clear(arr,x) memset(arr,x,sizeof(arr))
/
int n,m;
int sum1[5000005]={0};
char a[5000001];

int ans1[100001],ans2[100001];
int cnt1=0,cnt2=0;
int num0=0;
namespace LCT
{         //

};        //
/
void qin(int &ret)
{
 	 char c;ret=0;
 	 while((c=getchar())<'0'||c>'9');
 	 while(c>='0'&&c<='9')ret=ret*10+c-'0',c=getchar();
}

bool check(int s,int m,int cut)
{
 	 if(cut==1)
 	 {
	     if(abs(sum1[n]-sum1[s])<=m)
	         return 1;
	     else return 0;
	 }
 	 for(int i=s+1;i<=n-cut+1;i++)
 	 {
	     if(abs(sum1[i]-sum1[s])<=m&&check(i,m,cut-1)) return 1;
     }
     return 0;
}
bool dfs1(int s,int m,int cut)
{
 	 if(cut==1)
 	 {
	     if(abs(sum1[n]-sum1[s])<=m)
	     {
			 ans1[++cnt1]=n-s;
			 for(int i=1;i<=cnt1;i++) printf("%d ",ans1[i]);
	         return 1;
		 }
	     else return 0;
	 }
 	 for(int i=s+1;i<=n-cut+1;i++)
 	 {
	     ans1[++cnt1]=i-s;
		 if(abs(sum1[i]-sum1[s])<=m&&dfs1(i,m,cut-1))
	     {
		      return 1;
		 }
		 cnt1--;
     }
     return 0;
}
bool dfs2(int s,int m,int cut)
{
 	 if(cut==1)
 	 {
	     if(abs(sum1[n]-sum1[s])<=m)
	     {
			 ans2[++cnt2]=n-s;
			 for(int i=1;i<=cnt2;i++) printf("%d ",ans2[i]);
	         return 1;
		 }
	     else return 0;
	 }
 	 for(int i=n-cut+1;i>=s+1;i--)
 	 {
	     ans2[++cnt2]=i-s;
		 if(abs(sum1[i]-sum1[s])<=m&&dfs2(i,m,cut-1))
	     {
		      return 1;
		 }
		 cnt2--;
     }
     return 0;
}
/
void input()
{
     qin(n); qin(m);// getchar();
     for1(i,n)
     {
  		 a[i]=getchar()=='0'?-1:1;
  		 sum1[i]=sum1[i-1]+a[i];
  		 if(sum1[i]==0)num0++;
     }
}
void solve()
{
     ///init///
     int l=0,r=n/m+1,mid;
     int ans;
     calculate
     //if(num0>=m)r=0;
     while(l<r)
     {
         mid=(l+r)>>1;
         if(check(0,mid,m)) r=mid;
         else			    l=mid+1;
     }
     ans=l;
     l=1,r=1;
     dfs1(0,ans,m);
     puts("");
     //for1(i,n)printf("%d ",sum2[i]);
     dfs2(0,ans,m);
     /output/
     
     /*puts("");
     for(int i=cnt2;i>=1;i--) printf("%d ",ans2[i]);*/
}
/
int main()
{
     #ifndef _TEST
     freopen("villa.in","r",stdin); freopen("villa.out","w",stdout);
     #endif
     input();
     solve();
     #ifdef _TEST
     for(;;);
     #endif
     return 0;
}


这题尼玛用贪心、、咳咳、、、、

能贪个什么东西啊、、、

据网上神犇所说“易证:最优方案下,最大男女人数差ans=(abs(a[n])-1)/m+1(即上取整)”



AC Code~~~

最近是dev的tab出问题了吗?缩进成了这样、、、

//#define _TEST _TEST  
#include <cstdio>  
#include <cstring>  
#include <cstdlib>  
#include <iostream>  
#include <cmath>  
#include <algorithm>  
using namespace std;  
/  
#define for0(i,j) for(int i=0;i<j;i++)  
#define for1(i,j) for(int i=1;i<=j;i++)  
#define clear(arr,x) memset(arr,x,sizeof(arr))  
/  
const int maxn=5000000+10;  
int n,m;  
char s[maxn];  
int dif[maxn];  
int zero,ans,mis;  
int st[maxn];  
/  
inline int abs(int x){return x>0?x:-x;}  
void cal()//按每组人数字典序最小原则分组 
{
	zero=0,mis=0;
	memset(dif,0,sizeof(dif));
	for(int i=0;i<n;i++)
	{
		if(s[i]=='1')mis++;
		else mis--;
		dif[i]=mis;
		if(!mis)zero++; 
	}
	if(!dif[n-1] && zero>=m)
	{
		if(zero>=m)ans=0;
		else ans=1;
	}
	else ans=abs(abs(dif[n-1])-1)/m+1;
	int cnt=0,cur=-1;
	for(int i=0;i<n;i++)
	{
		if(cnt>=m-1)//如果已经分了m-1组了,则将后面n-i人作为第m组就完工了 
		{
			st[cnt++]=n-i;
			break;
		} 
		if(abs(dif[n-1]-dif[i])<=ans*(m-cnt-1))
		//后n-i人的男女差 = 总男女差 - 前面i人的男女差 <= 后m-cnt组*ans ,则可以按此分组                
		{
			st[cnt++]=i-cur;//将[cur+1,i]分为一组 
			cur=i;
		}
	}
}
/*void cal()  
{  
     int cnt=0,cur=-1;  
     zero=0,mis=0;  
     clear(dif,0);  
     for0(i,n)  
     {  
         if(s[i]=='1') mis++;  
         else          mis--;  
         dif[i]=mis;  
         if(!mis)zero++;  
     }  
     if(!dif[n-1]&&zero>=m) ans=0;  
     else                   ans=abs(abs(dif[n-1])-1)/m+1;  
     for0(i,n)  
     {  
         if(cnt>=m-1)  
         {  
             st[cnt++]=n-i;  
             break;  
         }  
         if(abs(dif[n-1]-dif[i])<=ans*(m-cnt+1))  
         {  
             st[cnt++]=i-cur;  
             cur=i;  
         }  
     }  
}  */
/  
void input()  
{  
     scanf("%d%d\n",&n,&m);  
     gets(s);  
}  
void solve()  
{  
     ///init///  
       
     calculate  
     cal();  
     for0(i,m) printf("%d ",st[i]);  
     puts("");  
     reverse(&s[0],&s[n]);  
     cal();  
     for0(i,m) printf("%d ",st[m-i-1]);  
     /output/  
       
}  
/  
int main()  
{  
     #ifndef _TEST  
     freopen("villa.in","r",stdin); freopen("villa.out","w",stdout);  
     #endif  
     input();  
     solve();  
     #ifdef _TEST  
     for(;;);  
     #endif  
     return 0;  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值