1. 题意梗概
给定一个数列 a,问能否选出任意数量的元素,使它们的乘积为 67。
- n≤5
- 1≤ai≤67
2. 逻辑推导
鉴于 n≤5,这个数量级可以直接用搜索搜,秒了。
等等,Div3T1 就搜索不太对吧,注意到 67 是一个质数。
根据唯一分解定理 67=671,那就只需要判断有没有 67 就行了。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12
| void solve( ) { cin >> n; bool ok67 = 0; for( int i = 1; i <= n; ++ i ) { int x; cin >> x; if( x == 67 ) ok67 = 1; } if( ok67 ) cout << "YES" << '\n'; else cout << "NO" << '\n'; }
|
1. 题面梗概
中译中:给定一个排列,你只能交换下标为 i 和 2i 的元素。问能否通过任意次交换将其排好序?
- 1≤n≤2⋅105
- 1≤ai≤n
2. 逻辑推导
注意到 任意次 和 交换 这两个条件,那么本质上就是在讨论 连通性。
我的写法是 并查集,把所有 i,2i 都合并,然后查询 a[i] 能不能交换到 i 即可。
排列的性质:由 n 个从 1 到 n 的不同整数按任意顺序组成。
当然这里还有别的实现方式,比如:
- a1,a2,a4,a8,… 能重排;
- a3,a6,a9,… 能重排;
- a5,a10,a15,… 能重排;
- a7,a14,a21,… 能重排;
那么只要处理 各个子数列 能否满足 a[i]==i 即可。
吐槽:这个子数列的划分有点素数筛的感觉了。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| int find( int x ) { if( fa[x] == x ) return fa[x]; else return fa[x] = find( fa[x] ); }
void merge( int x, int y ) { int rx = find( x ), ry = find( y ); if( rx != ry ) fa[rx] = ry; }
void solve( ) { cin >> n;
for( int i = 1; i <= n; ++ i ) fa[i] = i;
for( int i = 1; i <= n / 2; ++ i ) merge( i, 2 * i ); bool ok = 1; for( int i = 1; i <= n; ++ i ) { cin >> a[i]; if( find( i ) != find( a[i] ) ) ok = 0; } if( ok ) cout << "YES" << '\n'; else cout << "NO" << '\n'; }
|
1. 题意梗概
中译中:修改最少的骰子序列,使得相邻的骰子面在立方体上也是相邻的(即不相对且不相同)。
- 1≤n≤3⋅105
- 1≤ai≤6
2. 逻辑推导
骰子只有 6 个面,而且当前状态受到前一个状态的约束。那么这题就可以用一个 DP 来解决。
定义:设 dp[i][j] 表示前 i 个骰子合法,且第 i 个骰子面为 j 的最小修改数。
转移:dp[i][j]=min(dp[i−1][k])+cost(ai,j),条件 j=k 且 j+k=7,即不是 相同 且 不是 相对。
官方题解还有一种贪心的方法?感兴趣可以研究研究,我感觉不如 DP 直观,偷懒了。
下面简要介绍一下,想了想还是写一下吧。
对于任意两个面 S,T,总是存在一个面 U 与这两个面相邻。
- 若 S 和 T 相邻,则 U 是与 S 和 T 共角的任意一面。
- 若 S 和 T 相同,则 U 是和 S 相邻的面。
- 若 S 和 T 相对,则 U 是剩下任意四个面。
然后我们把序列 a,按照相对和相同面拆分成 m 个子段。(按照冲突的子段拆分)
比如说 [6,1,4,3,1,3,2,5,4,4] 就可以拆成 [6,1],[4,3],[1],[3],[2,5],[4,4]。
总次数为:4×⌊22⌋+2×⌊21⌋=4
每个由 k 个元素组成的子段,至少需要修改 ⌊2k⌋ 才能解决,那也可以在 O(n) 的时间拆分数列解决。
再比如说 [1,6,1,1,2,5,2,5,2,3,4,1,1] 变成了 [1,6,1,1],[2,5,2,5,2],[3,4],[1,1]。
总次数为:⌊25⌋+⌊24⌋+2×⌊22⌋=6
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void solve( ) { cin >> n; for( int i = 1; i <= n; ++ i ) cin >> a[i];
for( int j = 1; j <= 6; ++ j ) { if( a[1] == j ) dp[1][j] = 0; else dp[1][j] = inf; }
for( int i = 2; i <= n; ++ i ) { for( int j = 1; j <= 6; ++ j ) { dp[i][j] = inf; int cost = 1; if( a[i] == j ) cost = 0;
for( int k = 1; k <= 6; ++ k ) { if( j == k || j + k == 7 ) continue; dp[i][j] = min( dp[i][j], dp[i - 1][k] + cost ); } } }
int ans = inf; for( int j = 1; j <= 6; ++ j ) ans = min( ans, dp[n][j] ); cout << ans << '\n'; }
|
1. 题面梗概
中译中:给定 n 个位置的函数值,其中 ai 是隐藏的整数序列。已知所有的 f(1…n),求原序列 a。
f(x)=i=1∑nai∣i−x∣
- 2≤n≤3⋅105
- −1014≤f(i)≤1014
2. 逻辑推导
这个题看起来就不是很友善,但绝对值的核心在于 分段线性。我们可以先把柿子展开看看:
f(x)=i=1∑x(ai(x−i))+i=x+1∑n(ai(i−x))
既然是 线性 的,我们可以先差分看看性质。
df(x)=f(x+1)−f(x)=i=1∑x(ai(x+1−i))+i=x+1∑n(ai(i−x−1))−i=1∑x(ai(x−i))+i=x+1∑n(ai(i−x))
化简后得到柿子如下:记 Sx=∑i=1xai。
df(x)=i=1∑xai−i=x+1∑nai=Sx−(Sn−Sx)=2Sx−Sn
这里我们再差分一次(因为我们得到了 2Sx−Sn 这个前缀和关系,差分可以还原回正常序列)
ddf(x)=df(x)−df(x−1)=(2Sx−Sn)−(2Sx−1−Sn)=2(Sx−Sx−1)=2ax
然后我们就得到了ax 和 ddf 函数的关系了,至于两端的 a1 和 an 怎么求呢?我们可以从 df 函数的端点出发。
df(1)=2S1−Sn=2a1−Sn
df(n−1)=2Sn−1−Sn=2(Sn−an)−Sn=Sn−2an
至于这个 Sn 怎么求?我们可以从 f 函数的端点出发。
f(1)=i=1∑nai(i−1)f(n)=i=1∑nai(n−i)
那么就可以得到这个柿子了:
f(1)+f(n)=i=1∑n(ai⋅(n−1))=Sn⋅(n−1)
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void solve( ) { cin >> n; for( int i = 1; i <= n; ++ i ) cin >> f[i];
ll Sn = ( f[1] + f[n] ) / ( n - 1 );
for( int i = 1; i <= n; ++ i ) df[i] = f[i + 1] - f[i];
for( int i = 1; i <= n; ++ i ) ddf[i] = df[i] - df[i - 1];
a[1] = ( Sn + df[1] ) / 2;
for( int i = 2; i <= n - 1; ++ i ) a[i] = ddf[i] / 2;
a[n] = ( Sn - df[n - 1] ) / 2; for( int i = 1; i <= n; ++ i ) cout << a[i] << " "; cout << '\n'; }
|