CF1225G To Make 1

一、题目

点此看题

注意翻译中有两点没说到:\(a_i\) 不被 \(k\) 整除;\(\sum a_i\leq 2000\)

二、解法

不难写出 \(O(3^n\sum a_i)\) 的子集枚举,但是显然 \(\tt T\) 飞并且无法优化。

深究这样 \(dp\) 不行的原因:其实是插入的 \(x+y\) 需要立即除以 \(k\),这导致我们难以判断某一状态的优劣,难以去除无效转移;并且这个转移具有强烈的顺序性,只能通过暴力 \(dp\) 考虑所有的组合方式。

被逼入绝境的我们只能找结论了,注意一定要找必要条件再证明其充分性(充分条件真的找不出来),思考本题顺序性是最大的难点,那么我们找的结论就一定要帮助我们逃离顺序性的桎梏。

考虑合法解中每个数,每个数被 \(k\) 除的次数是不同的,那么我们就考虑这个被除的次数 \(b_i\),所有数被除以后加起来是 \(1\),那么必要条件显然是存在一个非负 \(b\) 序列使得:\(\sum a_i\cdot k^{-b_i}=1\)

然后考虑证明其充分性,也就是如果有 \(b\) 序列满足 \(\sum a_i\cdot k^{-b_i}=1\) 能推出一组合法解,证明可以考虑归纳法,首先序列长为 \(1/2\) 时是对的,当序列长为 \(n>2\) 时,设 \(\alpha=\max b_i\),如果两个 \(\alpha\) 合并之后能被 \(k\) 整除,就能让 \(b_i\) 变小归纳下去,否则再和 \(\alpha\) 合并, 因为分母是 \(k^{\alpha}\) 而且最后求和是 \(1\),所以不可能出现归纳不下去的情况,充分性得也。


根据这个结论设计 \(dp\),不难发现现在我们要决策 \(b\) 序列,如果你看懂了证明就会发现大的 \(b_i\) 内部合并完之后要到更小的 \(b_i\) 才行,所以我们\(b_i\) 从大到小的顺序来 \(dp\),但在实现中就把以前集合 \(s\) 中的 \(b_i\) 全部加 \(1\) 即可。

\(dp[s][p]\) 表示集合 \(s\) 的求和是 \(p\) 可不可行,转移:

  • 向集合中加入 \(b_i=0\) 的一个 \(a_i\)\(dp[s|2^i][p+a_i]\leftarrow dp[s][p]\)
  • 把集合中的 \(b_i\) 全部增加 \(1\)\(dp[s][p]\leftarrow dp[s][p\cdot k]\),这个要用完全背包的转移顺序。

不难发现可以用 \(\tt bitset\) 优化,时间复杂度 \(O(\frac{1}{w}n\cdot 2^n\cdot \sum a_i)\)

三、总结

找结论的小 \(\tt tirck\):考虑需要解决什么问题,找对应的结论;找必要性结论(充分性一般归纳证明)

决策具有某种性质的序列问题,用状压来定大小顺序,然后考虑整体加值。

#include <cstdio>
#include <bitset>
#include <iostream>
#include <queue>
using namespace std;
#define pii pair<int,int>
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,a[20],b[20];bitset<2005> dp[1<<16];
void dfs(int s,int p)
{
	if(!s) return ;
	if(p*k<=m && dp[s][p*k])
	{
		for(int i=0;i<n;i++)
			if(s&(1<<i)) b[i+1]++; 
		dfs(s,p*k);
		return ;
	}
	for(int i=0;i<n;i++)
		if((s&(1<<i)) && a[i+1]<=p && dp[s^(1<<i)][p-a[i+1]])
		{
			dfs(s^(1<<i),p-a[i+1]);
			return ;
		}
}
signed main()
{
	n=read();k=read();
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
		a[i]=read(),m+=a[i];
	for(int s=1;s<(1<<n);s++)
	{
		for(int i=1;i<=n;i++)
			if(s&(1<<i-1))
				dp[s]|=dp[s^(1<<i-1)]<<a[i];
		for(int i=m/k;i>=1;i--)//attention the order
			dp[s][i]=dp[s][i]|dp[s][i*k];
	}
	if(!dp[(1<<n)-1][1])
	{
		puts("NO");
		return 0;
	}
	puts("YES");
	dfs((1<<n)-1,1);
	priority_queue<pii> q;
	for(int i=1;i<=n;i++)
		q.push(make_pair(b[i],a[i]));
	while(q.size()>=2)
	{
		pii t1=q.top();q.pop();
		pii t2=q.top();q.pop();
		printf("%d %d\n",t1.second,t2.second);
		int x=t1.second+t2.second,y=t1.first;
		while(x%k==0 && y) y--,x/=k;
		q.push(make_pair(y,x));
	}
}
posted @ 2021-07-31 10:16  C202044zxy  阅读(372)  评论(0编辑  收藏  举报