problem
洛谷链接
solution
最后子集大小一定 ≥n−3\ge n-3≥n−3,下面考虑证明这个结论。
-
假设 n=2kn=2kn=2k。
∏i=1n(i!)=∏i=1k(2i−1)!(2i)!=∏i=1k(((2i−1)!)22i)=∏i=1k((2i−1)!)2⋅∏i=1k2i=∏i=1k((2i−1)!)2⋅2k⋅k!\prod_{i=1}^n(i!)=\prod_{i=1}^{k}(2i-1)!(2i)!=\prod_{i=1}^{k}\Big(\big((2i-1)!\big)^22i\Big)=\prod_{i=1}^k\big((2i-1)!\big)^2·\prod_{i=1}^{k}2i=\prod_{i=1}^k\big((2i-1)!\big)^2·2^k·k!∏i=1n(i!)=∏i=1k(2i−1)!(2i)!=∏i=1k(((2i−1)!)22i)=∏i=1k((2i−1)!)2⋅∏i=1k2i=∏i=1k((2i−1)!)2⋅2k⋅k!
- k=2tk=2tk=2t,只用扔掉 k!k!k! 这一项,就可以开出整根 ∏i=1k(2i−1)!⋅2t\prod_{i=1}^k(2i-1)!·2^t∏i=1k(2i−1)!⋅2t。
- k=2t+1k=2t+1k=2t+1,扔掉 k!k!k! ,再多扔一个 2!2!2!,仍可以开出上面的整根。
-
假设 n=2k+1n=2k+1n=2k+1。
直接扔掉 n!n!n!,就转化到了 n′=2kn'=2kn′=2k 的情况。
且如果情况满足 n≡3(mod4)n\equiv3\pmod4n≡3(mod4),答案一定是扔掉 2!,n−12!,n!2!,\frac{n-1}{2}!,n!2!,2n−1!,n!。
这里只是证明的答案的下界,但并非一定是最优答案的扔法(通过样例可知)。
接着我们需要考虑如何判断是否可以只用删除 1/21/21/2 个数。
要知道,决定一个数是否是平方数的关键在于质因子的幂次是否都是偶数。
也就是说要在尽可能地少扔数条件下,使得质因子幂次为奇数的都变成偶数。
只有出现次数的奇偶之分,我们想到了 0/10/10/1。
两个数相乘,对应质因子的幂次相加,二进制的加法相当于 010101 的异或。
但不可能对于每一个 iii 都把 nnn 以内的质数都开一个空间,然后储存奇偶之分,再异或。时间空间总得挂一个。
1e61e61e6 以内的质数个数 $\approx\frac{n}{\ln n}\approx 78498 $。我们采用 异或哈希。
对每一个质数分配随机的 646464 位整型权值,两个数的乘积变为两个数的哈希值的异或。
这样一来,对于出现偶数次的质数,异或后结果就是 000,只有出现奇数次的质数的权值会呈现在结果中。
质数 iii 的哈希值记为 gig_igi,同样还要知道 gi!g_{i!}gi!,这是很好求的 gi!=g(i−1)!⊕gig_{i!}=g_{(i-1)!}\oplus g_igi!=g(i−1)!⊕gi。
因为 111 肯定是必选的,请直接从头到尾忽视。
- 如果 val=⨁i=2ngi!=0val=\bigoplus_{i=2}^n g_{i!}=0val=⨁i=2ngi!=0,则答案为 nnn。
- 将 gi!g_{i!}gi! 通过
map
映射 iii。- 如果
map
中存在一个值为 valvalval,意味着可以删去这个 val→kval\rightarrow kval→k,答案为 n−1n-1n−1。 - 枚举删除的要删除的数 iii,考虑
map
是否存在 val⊕gi!val\oplus g_{i!}val⊕gi!,如果有则将这个映射的 kkk 一同删去,答案为 n−2n-2n−2。
- 如果
- 如果以上情况都没有发生,那么应当是 n≡3(mod4)n\equiv 3\pmod4n≡3(mod4) 的情况,直接删掉特定的三个数即可。
我也不知道这个哈希的冲撞概率是多少,太菜了 so vegetable!
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define int long long
map < int, int > mp;
int n, cnt;
int f[maxn], g[maxn], prime[maxn];signed main() {mt19937_64 Rand( time( 0 ) );scanf( "%lld", &n );iota( prime + 1, prime + n + 1, 1 );for( int i = 2;i <= n;i ++ ) if( prime[i] == i ) {for( int j = i;j <= n;j += i )prime[j] = min( prime[j], i );g[i] = Rand();}for( int i = 2;i <= n;i ++ ) {f[i] = f[i - 1];int x = i;while( x ^ 1 ) {f[i] ^= g[prime[x]];x /= prime[x]; }mp[f[i]] = i;}auto solve = [&] ( int n ) -> vector < int > {int ans = 0;for( int i = 2;i <= n;i ++ ) ans ^= f[i];if( ! ans ) return {};if( mp.find( ans ) != mp.end() ) return { mp[ans] };for( int i = 2;i <= n;i ++ )if( mp.find( ans ^ f[i] ) != mp.end() ) return { i, mp[ans ^ f[i]] };return { 2, n - 1 >> 1, n };};auto ans = solve( n );printf( "%lld\n", n - ans.size() );for( int i = 1;i <= n;i ++ )if( find( ans.begin(), ans.end(), i ) == ans.end() ) printf( "%lld ", i );return 0;
}