题目
题解
一道简单的状压的题。容易发现,最优答案构成的数列选的位置一定是有循环节的,容易发现在贪心策略下,相隔x+y的两个位置的选取状态一定相同,因为对于a、a+x、a+y、a+x+y四个位置,若a处选入,那么a+x、a+y处必定没选,那么a+x+y处为何空着不选?肯定选了更优。若a处不选,那么a+x、a+y肯定有一处选入,那么a+x+y处也不能选。所以我们设一个状态长度22的状压DP,dp[i][j]表示在i位置,前max(x,y)个数的选取状态为j的最大集大小,dp的时候用个滚动,复杂度。
其实这个证明并不是很严谨,很多是靠打表硬证的,正如YYDSJZM说过
如果什么东西都要证明,那我们和数竞有什么区别
还有一个特殊处理的地方也是我打表发现的,就是当x+y除不尽n,余下的那一点的最优解可能和前面的循环节不一样,因为前面的证明是考虑前后互相影响下最终稳定的最优解,尾巴处自然要例外(这算是勉强证明吧
然后我发现纯贪心在小范围内是有效的,所以我们把循环节从优到劣排序,不超时的情况下(也就是掐表),每次贪心构造尾端的解,然后记录最优答案。
代码
跑得慢但是速度稳定
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
#define uns unsigned
#define MAXN 100005
#define INF 0x3f3f3f3f
#define QB 50000000
using namespace std;
inline ll read(){
ll x=0;bool f=1;char s=getchar();
while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
return f?x:-x;
}
int x,y,m,dp[2][4194310],sr[4194310];
ll n,ans;
inline bool cmp(int a,int b){
return dp[(x+y)&1][a]>dp[(x+y)&1][b];
}
signed main()
{
n=read(),x=read(),y=read();
if(x>y)swap(x,y);m=(1<<y)-1;
for(int i=1;i<=x+y;i++){
for(int S=0,lim=(1<<min(i,y));S<lim;S++)dp[i&1][S]=0;
for(int S=0,lim=(1<<min(i,y));S<lim;S++){
dp[i&1][(S<<1)&m]=max(dp[i&1][(S<<1)&m],dp[~i&1][S]);
if(((S>>(x-1))&1)==0&&((S>>(y-1))&1)==0)
dp[i&1][(S<<1|1)&m]=max(dp[i&1][(S<<1|1)&m],dp[~i&1][S]+1);
}
}
for(int S=0;S<=m;S++)sr[S]=S;
sort(sr,sr+1+m,cmp);
for(int i=0,lim=min(QB/(x+y),m);i<=lim;i++){
ll res=dp[(x+y)&1][sr[i]]*(n/(x+y));
int len=n%(x+y),S=sr[i];
for(int j=1;j<=len;j++)
if(((S>>(x-1))&1)==0&&((S>>(y-1))&1)==0)res++,S=(S<<1|1)&m;
else S=(S<<1)&m;
ans=max(ans,res);
}
printf("%lld\n",ans);
return 0;
}