【Codeforces#596】E. To Make 1

27 篇文章 0 订阅
9 篇文章 0 订阅

Descripton

传送门

  • 有n个数不被k整除的数a[i]。
  • 每一次选择两个数x,y,删去这两个数并加入f(x+y):
    f ( x ) = ( x m o d    k = 0 ) ? f ( x / k ) : x f(x)=(x \mod k=0)?f(x/k):x f(x)=(xmodk=0)?f(x/k):x
  • 求最后能不能得到1。如果能,输出每一次将哪两个数删去。
  • n<=16, ∑ a [ i ] \sum a[i] a[i],k<=2000

Solution

  • 一道让我无从下手的题目。
  • 由于会除以k,所以最终的数可以表示成 ∑ a i ∗ k − b i = 1 \sum a_i*k^{-b_i}=1 aikbi=1
  • 如果我们知道了b的话,就可以推出操作的方案了。
  • 具体来说,假设 B = m a x ( b i ) B=max(b_i) B=max(bi),一定有两个bi同时等于B。否则上式两边同时乘kB就变成
    a + ∑ a i ∗ k B − b i = k B a+\sum a_i*k^{B-b_i}=k^B a+aikBbi=kB
  • a不是k的倍数,左边不是,右边是,不成立。
  • 那么这样就可以将这两个数和在一起了。并加入新的a和b。这个等式还是成立的。直到最后一个。
  • 现在需要求出b。
  • 状压DP,设f[S][x]=0/1,表示已经选了S集合里面的ai, ∑ a i ∗ k − b i = x \sum a_i*k^{-b_i}=x aikbi=x是否成立。
  • 每一次枚举一个没有选过的a,转移到f[S+(1<<i)][x+ai]。
  • 并且可以转移到f[S][x/k].
  • 这个转移相当于是按照bi从小到大的顺序加入a。
  • 最后判定f[(1<<n)-1][1]。倒退回去即可得到b。
  • 但是这样转移是 O ( 2 n n ∑ a [ i ] ) O(2^nn\sum a[i]) O(2nna[i])的。考虑把x那一维压进bitset里面。那前一个转移到x+ai的转移就可以通过左移ai位再或过去完成(这个我想了好久,之前一直以为是将S压起来)。时间除以32就可以过了。
  • 总之,这题需要先化简每一个a对答案的贡献,再DP计算出b,用bitset优化。再通过b还原,其中还需要发现合并的性质。
  • 一道神奇的题目。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<bitset>
#define maxn 16
#define maxm 2005
using namespace std;

int n,k,a[maxn],sum,i,j,S,x,cnt,b[maxn];
bitset<maxm> f[1<<maxn];

void dg(int tot){
	if (tot==n) return; 
	int B=0;
	for(int i=0;i<=n-tot;i++) 
		B=max(B,b[i]);
	int t1=0,t2=0;
	for(int i=0;i<=n-tot;i++) if (b[i]==B){
		if (!t1) t1=i; else 	
			{t2=i;break;}
	}
	printf("%d %d\n",a[t1],a[t2]);
	int cnt=0,tmp;
	for(tmp=a[t1]+a[t2];tmp%k==0;tmp/=k) cnt++;
	b[t1]=B-cnt,a[t1]=tmp;
	for(int i=t2;i<n-tot;i++) a[i]=a[i+1],b[i]=b[i+1];
	dg(tot+1);
}

int main(){
	scanf("%d%d",&n,&k);
	for(i=0;i<n;i++) scanf("%d",&a[i]),sum+=a[i];
	f[0][0]=1;
	for(S=0;S<1<<n;S++) if (f[S].count()>0){
		for(i=sum;i>=1;i--) if (i%k==0)
			f[S][i/k]=f[S][i/k]|f[S][i];
		for(i=0;i<n;i++) if (!((S>>i)&1))
			f[S|(1<<i)]=f[S|(1<<i)]|(f[S]<<a[i]);
	}
	if (f[(1<<n)-1][1]){
		printf("YES\n");
		S=(1<<n)-1,x=1,cnt=0;
		for(i=1;i<=n;i++){
			while (x*k<=sum&&f[S][x*k]) x*=k,cnt++;
			for(j=0;j<n;j++) if (x>=a[j]&&(S&(1<<j))&&(f[S^(1<<j)][x-a[j]])){
				b[j]=cnt,S^=1<<j,x-=a[j];
				break;
			}
		}
		dg(1);
	} else printf("NO\n");
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值