Problem
给出n(≤800)只兔子在m(≤800)天内是否会被抓,然后求出一种在m天内每天派出k(≤n)只兔子的方案,要求保证该方案每次出去的兔子都不会被吃且相邻两天派出兔子的差异数不超过L(≤k)。
Solution
比赛时我觉得这题很奇怪,感觉可能是道贪心题,并且想了一下,但是思考方向有点问题;随后我又想到了网络流,但没想到怎么建图;于是最后就打了暴力。
正解的话,其实贪心和网络流都可以。
网络流:40~100points
首先,对于每一只兔子,我们都给它拆成m个点,分别表示m天中每一天的它。
由于我们要求保证每次出去的兔子都不会被吃,那么若有一个点会被吃,我们当然不管它。
那么我们就要保证相邻两天派出兔子的差异数不超过L了。这里的方法比较巧妙,自己想应该比较难想,那就是在相邻两天中设置两个转换点u1,u2。设这相邻的两天为p,q,且p+1=q,那么我们从第p天中的所有点向u1各连一条容量为1的边,从u1向u2连一条容量为L的边,从u2向第q天中的所有点各连一条容量为1的边。但是我们知道,如果有只兔子两天都不会被吃,那么我们让它两天都出来是不会增加差异数的,所以我们从第p天中的所有兔子向第q天中的它连一条容量为1的边。
但是如果我们按上述所做,那么每一个代表兔子的点可能会经过超过2的流量。比如举个浅显的栗子:设有兔1和兔2,L为1,第一天两兔都出去了,但是我们从第一天的兔1向第二天的兔1流了1的流量,又从第一天的兔2向第二天的兔1流了1的流量,这样就会导致第二天的兔1被派出了两次。如何防止这种情况发生呢?
其实比较简单,就是把每一天中的每一只兔子都拆成两个点,从前排的点向后排的点连一条容量为1的边,前排的点负责接收,后排的点负责排出。那么由于有了这条神奇的边的限制,每一天中的每一只兔子所对应的点就最多只会经过1的流量了。
于是按照常规套路,新建源点和汇点,从源点向第一天的点各连一条容量为1的边,从第m天的点向汇点各连一条容量为1的边,跑一遍最大流,看看最大流是否不小于k,小于则无解。但如何求出一种方案呢?
其实也很简单,你直接看一下哪些边被流过了,那么说明我们的方案会经过那条边。然后有可能我们的方案中的一天会出现超过k只兔子(毕竟我们可以某些经过转换点u1,u2,某些直接流向下一天的同一只兔子),那么我们随便选k只兔子即可,毕竟这样做差异数会只少不多。
时间复杂度:
O(玄)
O
(
玄
)
。友情提示:根据我们使用算法的不同,打网络流会得到不同的分数。由于本题中的图较为稠密,打dinic在JZOJ能有90points。100points,则可能是高标推进。
Code
由于lyl试过了,但没过,所以我并没有打网络流。
贪心:100points
首先,我在比赛时就已经想到了一种最优策略:即不断地派出能持续最多天数的兔子。但是由于我的思考方向有点问题(我想的是先算出每只兔子的各段持续天数,然后丢进堆里,不断取最长),我不假思索地否定了我的想法。
但其实正解也差不多,只不过它是对于每一天都要选择从这天开始的、能持续最多天数的兔子。但是我们还得保证差异数≤L。
于是我们可以设a[i][j]表示第i天、第j只兔子往后最多能持续的天数(若它在第i天就会被吃则为0),然后先将第一天的兔子按a从大到小排个序,选择前k个。对于每个第i天(i>1),我们将第i-1天选择的兔子的信息(a和编号)存储在p数组里,将其他未选的兔子的信息存储在q数组里,那么将p从小到大排序,将q从大到小排序,然后将这两个数组的前L个值交换,直到当前的p[j]≥q[j]。
正确性显然。
时间复杂度:
O(nmlog2n)
O
(
n
m
l
o
g
2
n
)
。
Code
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 810
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
int i,j,l,n,m,k,L,ans[N][N];
char ch,map[N][N];
bool bz[N];
struct rabbit
{
int num,len;
}a[N][N],p[N],q[N];
inline bool operator<(const rabbit&a,const rabbit&b)
{
return a.len<b.len;
}
inline bool operator>(const rabbit&a,const rabbit&b)
{
return a.len>b.len;
}
inline bool cmp(rabbit a,rabbit b)
{
return a>b;
}
inline void print()
{
printf("-1");
exit(0);
}
int main()
{
scanf("%d%d%d%d",&n,&m,&k,&L);
fo(i,1,n)
{
do
ch=getchar();
while(ch!='0'&&ch!='1');
map[i][1]=ch;
fo(j,2,m)map[i][j]=getchar();
}
fd(i,m,1)
fo(j,1,n)
a[i][j].num=j,a[i][j].len=map[j][i]=='1'?1+a[i+1][j].len:0;
memcpy(p,a[1],sizeof p);
sort(p+1,p+n+1,cmp);
fo(i,1,k)
{
ans[1][i]=j=p[i].num;
if(map[j][1]=='0')print();
}
fo(i,2,m)
{
memset(bz,1,sizeof bz);
fo(j,1,k)p[j].len--,bz[p[j].num]=0;
l=0;
fo(j,1,n)
if(bz[j])
q[++l]=a[i][j];
sort(p+1,p+k+1);
sort(q+1,q+l+1,cmp);
fo(j,1,min(L,l))
{
if(!(p[j]<q[j]))break;
swap(p[j],q[j]);
}
fo(j,1,k)
{
if(!p[j].len)print();
ans[i][j]=p[j].num;
}
}
fo(i,1,m)
{
sort(ans[i]+1,ans[i]+k+1);
fo(j,1,k)
{
l=ans[i][j];
printf(j<k?"%d ":"%d\n",l);
}
}
}