洛谷P13030 [GCJ 2021 #1B] Subtransmutation

0x1f 关于猜想

看完题目后,注意力好的同学可能会发现:有解的充要条件是所有所需的金属编号模 \(\gcd(A,B)\) 同余。

0x2f 关于证明:

根据题意,我们可以将 \(i\) 分解成 \(j\) 这个操作,看作是 \(i\) 经过若干次减 \(A\) 和若干次减 \(B\) 得到了 \(j\),即 \(i-Ax-By=j\)

\(d=\gcd(A,B)\)

必要性

我们知道,最终的答案 \(S\) 经过分解后能够得到所有我们所需要的金属。对于每一块所需的金属编号 \(M_i\),都存在自然数 \(x_i\)\(y_i\) 使得 \(S-Ax_i-By_i=M_i\),变形可得 \(S-M_i=Ax_i+By_i\)

由于 \(d\mid (Ax_i+By_i)\),所以 \(d\mid (S-M_i)\),即 \(S \equiv M_i (\bmod d)\)

不难得出,所有的所需编号都模 \(d\) 同余,该命题得证。

充分性

先考虑只有两块金属 \(M_1\)\(M_2\) 的情况:

我们知道若有解,就一定存在一块金属 \(S'\) 使得它能够分解成 \(M_1\)\(M_2\),即 \(S'-Ax_1-By_1=M_1\)\(S'-Ax_2-By_2=M_2\),为满足题目条件,我们就令 \(x_1,x_2,y_1,y_2 \in N_+\)。问题就是是否存在这样的 \(x_1,x_2,y_1,y_2\)

我们将上面的两个公式稍加变形:

将两式合并。

\[\begin{cases} S'-Ax_1-By_1=M_1\\ S'-Ax_2-By_2=M_2 \end{cases} \]

因为 \(M_2 \equiv M_1 \mod d\)。所以 \(d\mid M_2-M_1\)

我们令 \(M_2-M_1=kd\)。最终,公式就变成了 \(A(x_1-x_2)+B(y_1-y_2)=kd\)

有了上面的推论,无解情况就很好判断了。那么其他的就是有解情况了。

我们可以保证其他情况都有解,且这个解与前面的所有所需编号模 \(\gcd(A,B)\) (即上文中的 \(d\))同余。因此,我们只要从 \(n\) 出发,枚举所有可能的 \(S\) 即可。

我们用 \(mp\) 来记录编号与现有的数量,由于分解得到的编号一定是从大到小的,所以我们将 \(mp\) 按键升序排列。从 \(S\) 出发,一直分解,当遍历到 \(j\) 时,它的数量已经是固定的,若 \(j\) 是我们最终需要的金属,先判断够不够,不够直接退出循环,够了则减去所需量,再进行分解。知道最后全部分解完后,\(mp\) 为空,结束遍历。

最后,我们遍历数组 \(now\),若全部金属都已足够,就可以输出答案结束循环了。

0x3f 关于代码

\(\huge \mathscr{Code}\)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
int T,n,m,A,B,z,U[N],now[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	for(int _=1;_<=T;_++){
		cin>>n>>A>>B;
		int g = __gcd(A,B),flag = 1;
		m = -1;
		for(int i=1;i<=n;i++){
			cin>>U[i];
			if(U[i]){
				if(m==-1) m = i%g;
				else flag &= (i%g==m);
			}
		}
		if(!flag){
			cout<<"Case #"<<_<<": "<<"IMPOSSIBLE\n";
			continue;
		}
		int pos = n;
		while(true){
			for(int j=1;j<=n;j++) now[j] = U[j];
			map<int,int,greater<int>> mp;
			mp[pos] = 1;
			for(auto e:mp){
				int key = e.first,val = e.second;
				if(key<=n && now[key]){
					if(now[key]>val) break;
					else{
						val -= now[key];
						now[key] = 0;
					}
				}
				if(key>A) mp[key-A] += val;
				if(key>B) mp[key-B] += val;
			}
			bool flagg = 1;
			for(int j=1;j<=n;j++) flagg &= (now[j]==0);
			if(flagg){
				cout<<"Case #"<<_<<": "<<pos<<'\n';
				break;
			}
			pos += g;
		}
	}
	return 0;
}

posted @ 2025-08-11 16:40  OrangeRED  阅读(32)  评论(0)    收藏  举报