noip2021训练3(CF)
构造场
CF1348D Phoenix and Science
Description
在第 \(1\) 天,有一个质量为 \(1\) 的细菌
接下来每一天,\(1\) 个细菌可以分裂成 \(2\) 个,然后每个细菌的质量 \(+1\)。
给定一个 \(n\),求在若干天后细菌质量和为 \(n\) 的方案。
输出最小的天数和每天分裂出多少个细菌,如果不存在方案输出 \(-1\)。
\(2\le n \le 10^9\)
Solution
因为所有细菌的质量每天都会 \(+1\),所以不难发现早分裂比晚分裂更优。
每个细菌只能分裂成两个细菌,类似于二进制拆分,拆出来的结果就是每天细菌的个数,再做个差分就是每天分裂的个数了。
注意二进制拆分后要排序,最后可能会多出来一个数。
Code
#include <bits/stdc++.h>
using namespace std;
int ans[35], cnt;
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int n;
scanf("%d", &n);
int t = 1;
cnt = 0;
while(t <= n)
{
ans[++cnt] = t;
n -= t;
t <<= 1;
}
if(n) ans[++cnt] = n;
sort(ans + 1, ans + 1 + cnt);
printf("%d\n", cnt - 1);
for(int i = 2; i <= cnt; i++)
printf("%d ", ans[i] - ans[i - 1]);
putchar('\n');
}
return 0;
}
CF1348B Phoenix and Beauty
Description
给出一个长度为 \(n\) 的序列 \(a\ (1\le a_i \le n)\),在其中任意位置插入若干个 \([1,n]\) 中的数,使得新序列中 \(b\) 的连续 \(k\) 项和都相等。
你可以输出任意一组长度在 \([n,10^4]\) 之间的解,若无解输出 \(-1\)。
\(1\le k \le n \le 100\)
Solution
不难想到要构造一个长为 \(k\) 的循环节。
注意到 \(100\times100=10^4\),所以考虑暴力 \(O(nk)\) 的做法,对 \(a\) 序列中的每一个数都补成长为 \(k\) 的循环节。
循环节就是 \(a\) 序列中不同的数,如果个数 \(>k\) 则无解,如果 \(<k\),可以在后面补 \(1\) 。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int T, n, m, a[N], t[N];
vector <int> ans;
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
memset(t, 0, sizeof(t));
ans.clear();
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
if(!t[a[i]]) ans.push_back(a[i]);
t[a[i]] = 1;
}
if((int)ans.size() > m)
{
printf("-1\n");
continue;
}
printf("%d\n", n * m);
for(int i = 1; i <= n; i++)
{
for(auto j : ans) printf("%d ", j);
for(int j = (int)ans.size() + 1; j <= m; j++)
printf("1 ");
}
printf("\n");
}
return 0;
}
CF1304D Shortest and Longest LIS
Description
给定一个有 \(n\) 个元素的排列中相邻两个元素的大小关系,分别构造一组解,使得序列的 \(LIS\) 最长和最短。
\(2\le n \le 2\times 10^5\)
Solution
在构造最长时,先将序列初始化成 \([1,n]\) 的排列,这样是最长的,然后将输入中连续的 < 取出来,并将这一段区间翻转。
感性理解一下 \(LIS\) 是最长的。
最短同理。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int T, n;
char s[N];
int ans[N];
void Shortest_LIS()
{
for(int i = 1; i <= n; i++)
ans[i] = n - i + 1;
for(int i = 1, j; i <= n; i = j + 1)
{
j = i;
while(s[j] == '<') j++;
for(int k = j; k >= i; k--)
printf("%d ", ans[k]);
}
putchar('\n');
return;
}
void Longest_LIS()
{
for(int i = 1; i <= n; i++)
ans[i] = i;
for(int i = 1, j; i <= n; i = j + 1)
{
j = i;
while(s[j] == '>') j++;
for(int k = j; k >= i; k--)
printf("%d ", ans[k]);
}
putchar('\n');
return;
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d%s", &n, s + 1);
Shortest_LIS();
Longest_LIS();
}
return 0;
}
CF1312E Array Shrinking
Description
给你一个长度为 \(n\) 的数组 \(a\),每次你可以进行以下两步操作:
- 找到 \(i \in [1, n)\),使得 \(a_i = a_{i + 1}\)
- 将 它们 替换为 \(a_i + 1\)
每轮操作之后,显然数组的长度会减小 \(1\),问剩余数组长度的最小值。
\(1\le n \le 500,1\le a_i \le 1000\)
Solution
本题并不是构造(
考虑 dp
设 \(f_{i,j}\) 表示区间 \([i,j]\),最少能变成几个数,\(g_{i,j}\) 表示若区间 \([i,j]\) 能变成一个数那这个数是多少。
转移也比较简单
\(f_{i,j}=min(f_{i,j},f_{i,k}+f_{k+1,j})\ (i\le k <j)\)
若 \(f_{i,k}=1\ \& \ f_{k+1,j}=1\ \&\ g_{i,k}=g_{k+1,j}\)
\(f_{i,j}=1,g_{i,j}=g_{i,k}+1\)
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, f[N][N], g[N][N];
int main()
{
scanf("%d", &n);
memset(f, 0x3f, sizeof(f));
for(int i = 1; i <= n; i++)
{
scanf("%d", &g[i][i]);
f[i][i] = 1;
}
for(int len = 2; len <= n; len++)
{
for(int i = 1; i + len - 1 <= n; i++)
{
int j = i + len - 1;
for(int k = i; k < j; k++)
{
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
if(f[i][k] == 1 && f[k + 1][j] == 1 && g[i][k] == g[k + 1][j])
f[i][j] = 1, g[i][j] = g[i][k] + 1;
}
}
}
printf("%d\n", f[1][n]);
return 0;
}
CF1110E Magic Stones
Description
给定一个 \(n\) 个数的初始序列 \(c\) 和目标序列 \(t\),一次操作选择 \(i\in (1,n)\),使 \(c_i\) 变为 \(c_i'=c_{i+1}+c_{i-1}-c_i\)
是否能通过若干次操作,使得 \(c_i=t_i\)
\(2\le n \le 10^5,0\le c_i,t_i \le 2\times 10^9\)
Solution
比较巧妙
操作后的序列会变成 \(c_{i-1},c_{i+1}+c_{i-1}-c_i,c_{i+1}\)
发现差分数组为 \(c_{i+1}-c_i,c_i-c_{i-1}\)
而原序列的差分数组为 \(c_i-c_{i-1},c_{i+1}-c_i\)
值相同!!!
然后代码实现就比较简单了
只需要判断初始序列和目标序列的差分数组是否相同
无解还需要判断第一个和最后一个是否相同,因为操作无法修改这两个数。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n;
map <int, int> mp;
int main()
{
scanf("%d", &n);
int x, y;
for(int i = 1, a, b = 0; i <= n; i++, b = a)
{
scanf("%d", &a), mp[a - b]++;
if(i == 1) x = a;
if(i == n) y = a;
}
for(int i = 1, a, b = 0; i <= n; i++, b = a)
{
scanf("%d", &a), mp[a - b]--;
if(mp[a - b] < 0)
{
printf("No\n");
return 0;
}
if((i == 1 && a != x) || (i == n && a != y))
{
printf("No\n");
return 0;
}
}
printf("Yes\n");
return 0;
}
CF1110C Meaningless Operations
Description
定义函数 \(f(a)\) 的值为:
\(f(a)=\max_{0<b<a}\gcd(a\oplus b,a\ \&\ b)\)
给出 \(q\) 个询问,每个询问为一个整数 \(a_i\)。你需要对于每个询问,求出 \(f(a_i)\) 的值。
Solution
打表找规律
发现只要 \(a \neq 2^k-1\),那么 \(f(a)=2^k-1\),其中 \(2^k\) 为大于等于 \(a\) 的第一个 \(2\) 的整次幂。
对于 \(a=2^k-1\),如果看不出来规律,直接打表也是可以的
规律就是 \(f(a)=\dfrac{a}{mindiv}\)
这种题打表最舒服了
Code
#include <bits/stdc++.h>
using namespace std;
int main()
{
// for(int i = 1; i <= 1e8; i++)
// {
// if(i != pow(2, (int)log2(i) + 1) - 1) continue;
// int ans = 1;
// for(int j = 1; j < i; j++)
// if(__gcd(i ^ j, i & j) > __gcd(i ^ ans, i & ans)) ans = j;
// cout << i << " " << log2(i) << ": " << ans << " " << __gcd(i ^ ans, i & ans) << endl;
// }
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
int a;
scanf("%d", &a);
int ans = pow(2, (int)log2(a) + 1) - 1;
if(a == ans)
{
for(int i = 2; i * i <= a; i++)
if(a % i == 0)
{
ans = a / i;
break;
}
if(ans == a) ans = 1;
}
printf("%d\n", ans);
}
return 0;
}
CF540C Ice Cave
Description
给你一个 \(n×m\) 的表格,每个点有两种状态,一种是好冰(‘.’),一种是碎冰(‘X’),并且好冰被走过后就变成碎冰了。
然后再告诉你起点和终点四个数 \((t1,c1),(t2,c2)\),问起点是否能到终点。
保证起点是碎冰,终点不一定,最后要求掉进起点(即是碎冰,可以踩一下到另一个点再回来),在这期间(除了起点和终点)不得掉进任何一个碎冰,如果可行输出 YES,不行 NO。
Solution
也不是构造
普通的 dfs,只需要将 \(vis\) 数组做点变化,存一下是第几次到的即可。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m, sx, sy, tx, ty;
char s[N][N];
bool vis[N][N];
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
bool flag;
void dfs(int x, int y)
{
if(x == tx && y == ty && vis[x][y] == 1)
{
flag = true;
return;
}
if(vis[x][y] == 1 || flag) return;
vis[x][y] = 1;
for(int i = 0; i < 4; i++)
{
int wx = x + dx[i], wy = y + dy[i];
if(wx < 1 || wx > n || wy < 1 || wy > m) continue;
dfs(wx, wy);
if(flag) return;
}
return;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%s", s[i] + 1);
for(int j = 1; j <= m; j++)
if(s[i][j] == 'X') vis[i][j] = 1;
}
scanf("%d%d", &sx, &sy);
scanf("%d%d", &tx, &ty);
for(int i = 0; i < 4; i++)
{
int x = sx + dx[i], y = sy + dy[i];
if(x < 1 || x > n || y < 1 || y > m) continue;
dfs(x, y);
if(flag) break;
}
printf(flag ? "YES" : "NO");
return 0;
}
CF989C A Mist of Florescence
Description
让你构造一个\(n\times m\) 矩阵,这个矩阵由 \(4\) 种字符填充构成,给定 \(4\) 个整数,即矩阵中每种字符构成的四联通块个数,\(n,m\) 需要你自己定,但是不能超过 \(50\)。
\(1\le a,b,c,d \le 100\)
Solution
既然最大 \(50\times 50\),那就全用满,初始为 \(ABCD\) 各占一块
A | B
——|——
C | D
然后将 A 不够的在 B 中补,B 不够的在 C 中补,以此类推
因为最大不超过 \(100\) 个联通块,而 \(25\times 25 = 625\),显然是够的。
Code
#include <bits/stdc++.h>
using namespace std;
int a, b, c, d;
char ans[55][55];
void init(int x, int y, char c)
{
for(int i = x; i <= x + 24; i++)
for(int j = y; j <= y + 24; j++)
ans[i][j] = c;
return;
}
void work(int x, int y, char c, int num)
{
for(int i = x + 1; i <= x + 24 && num; i += 2)
for(int j = y + 1; j <= y + 24 && num; j += 2)
ans[i][j] = c, num--;
return;
}
void print()
{
printf("50 50\n");
for(int i = 1; i <= 50; i++)
{
for(int j = 1; j <= 50; j++)
putchar(ans[i][j]);
putchar('\n');
}
return;
}
int main()
{
scanf("%d%d%d%d", &a, &b, &c, &d);
init(1, 1, 'A');
init(1, 26, 'B');
init(26, 1, 'C');
init(26, 26, 'D');
work(1, 26, 'A', a - 1);
work(26, 1, 'B', b - 1);
work(26, 26, 'C', c - 1);
work(1, 1, 'D', d - 1);
print();
return 0;
}
HDU6592 Beauty Of Unimodal Sequence
Description
给出 \(n\) 个数的序列 \(a_1,a_2\dots a_n\),找出 最长的单峰序列(先严格递增后严格递减,允许仅严格递增或仅严格递减)
要求分别输出所有最长的单峰序列 字典序最小 和 字典序最大 的序列(按其下标排序,输出时也是输出下标)
\(1\le n \le 3 \times 10^5,1\le a_i \le 10^9\)
Solution
树状数组+贪心
\(f_i\) 表示以 \(a_i\) 为开头的最长下降子序列长度
\(g_i\) 表示以 \(a_i\) 为开头的最长单峰子序列长度
这个可以用权值树状数组或权值线段树来维护
最长下降子序列就不说了,来说说最长单峰子序列
其实和最长下降子序列差不多,只是要维护后缀最大值,在查询时
\(g_i=max(f_i,qry(a_i+1)+1)\)
维护后缀只需要把插入改成 \(x>0\),把查询改成 \(x\le n\)
有了这两个数组后,就可以贪心构造序列了
字典序最小:正着枚举,能选就选,对于 \(a_i\),如果在选了 \(a_i\) 后可以达到最长,那就选上。
字典序最大:要把 \(a\) 数组反过来,其余同理,就是在输出时也要反着输出,并且要把下角标对应回去。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int n, a[N], b[N], tot;
int c[N], f[N], g[N], len;
int pre, cnt, ans[N];
void add1(int x, int y)
{
for(; x <= tot; x += x & -x)
c[x] = max(c[x], y);
}
int qry1(int x)
{
int res = 0;
for(; x; x -= x & -x)
res = max(res, c[x]);
return res;
}
void add2(int x, int y)
{
for(; x; x -= x & -x)
c[x] = max(c[x], y);
}
int qry2(int x)
{
int res = 0;
for(; x <= tot; x += x & -x)
res = max(res, c[x]);
return res;
}
void prework()
{
pre = 0, cnt = 0, len = 0;
memset(c, 0, sizeof(c));
for(int i = n; i >= 1; i--)
{
f[i] = qry1(a[i] - 1) + 1;
add1(a[i], f[i]);
}
memset(c, 0, sizeof(c));
for(int i = n; i >= 1; i--)
{
g[i] = max(f[i], qry2(a[i] + 1) + 1);
add2(a[i], g[i]);
len = max(len, g[i]);
}
return;
}
void solve()
{
prework();
for(int i = 1; i <= n; i++)
{
if(a[i] > pre && g[i] + cnt == len)
{
ans[++cnt] = i;
pre = a[i];
}
else if(a[i] < pre && f[i] + cnt == len)
{
ans[++cnt] = i;
pre = a[i];
}
}
return;
}
int main()
{
while(scanf("%d", &n) != EOF)
{
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]), b[i] = a[i];
sort(b + 1, b + 1 + n);
tot = unique(b + 1, b + 1 + n) - b - 1;
for(int i = 1; i <= n; i++)
a[i] = lower_bound(b + 1, b + 1 + tot, a[i]) - b;
solve();
for(int i = 1; i <= cnt; i++)
printf("%d%c", ans[i], i < cnt ? ' ' : '\n');
reverse(a + 1, a + 1 + n);
solve();
for(int i = cnt; i >= 1; i--)
printf("%d%c", n - ans[i] + 1, i > 1 ? ' ' : '\n');
}
return 0;
}

浙公网安备 33010602011771号