CSP-S 40(爆零记)
10.27
赤到了。
第一次爆蛋。
t1
特判没卡掉11个人。
乐死了。
暴力有80pts。
正解:
发现值域很小只有1000,从此入手。
先预处理 1000 以内的素数,发现很少只有168个,空间可开下,这启发我们对于每个素数记录东西。
开vector \(d\) 存每个位置上的数分解质因数后含有的素数下标。
限制了 \(i\le j\) ,于是我们倒序处理,用后面的信息更新之前的信息。
数组 \(dd_{i,j}\) 表示当前含有第 \(i\) 个素数的位置所能到达的最小的含有第 \(j\) 个素数的位置。
数组 \(idd_{i,j}\) 表示当前第 \(i\) 个位置所能到达的最小的含有第 \(j\) 个素数的位置。
两者相互转移即可。
注意一大堆特判。
特别的:因为 \(\gcd(0,x)=x\) ,所以 \(0\) 可做任意两点间的中转点。
记数组 \(spe_i\) 表示由于 \(0\) 的存在,第 \(i\) 位置可到达的最近距离(以后也均可达)。
同时转移即可。
还是注意特判!!!
code
可恶的0与1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 170;
bool flag[N];
int pri[M], cnt;
int n, q;
vector<int> d[N];
int a[N];
int id_d[N][M], dd[M][M], spe[N];
inline void xxs()
{
for (int i = 2; i <= 1000; ++i)
{
if (!flag[i])
pri[++cnt] = i;
for (int j = 1; j <= cnt; ++j)
{
if (i * pri[j] > 1000)
break;
flag[i * pri[j]] = 1;
if (i % pri[j] == 0)
break;
}
}
}
signed main()
{
freopen("gcd.in", "r", stdin);
freopen("gcd.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
xxs();
cin >> n >> q;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
for (int j = 1; j <= cnt; ++j)
{
if (a[i] % pri[j] == 0)
d[i].emplace_back(j);
if (pri[j] > a[i])
break;
}
}
memset(id_d, 0x3f, sizeof(id_d));
memset(dd, 0x3f, sizeof(dd));
memset(spe, 0x3f, sizeof(spe));
for (int i = n; i; --i)
{
if (!a[i])
{
spe[i] = i;
continue;
}
spe[i] = spe[i + 1]; // 0及其之后都合法
for (auto j : d[i])
{
for (int k = 1; k <= cnt; ++k)
id_d[i][k] = min(id_d[i][k], dd[j][k]);
}
for (auto j : d[i])
dd[j][j] = i, id_d[i][j] = i;
for (auto j : d[i])
{
for (int k = 1; k <= cnt; ++k)
dd[j][k] = min(dd[j][k], id_d[i][k]);
}
}
int x, y;
while (q--)
{
cin >> x >> y;
if (x == y)
puts("Shi");
else if (a[x] == 1 || a[y] == 1)
puts("Fou");
else if (!a[x] && !a[y])
{
for (int i = x + 1; i < y; ++i)
if (a[i] > 1)
{
puts("Shi");
goto con;
}
puts("Fou");
}
else if (y >= spe[x])
puts("Shi");
else
{
for (auto i : d[y])
{
if (id_d[x][i] <= y)
{
puts("Shi");
goto con;
}
}
puts("Fou");
}
con:;
}
return 0;
}
t2
构造题。
其实不难,赛时被 t1 恶心到了,看到 t2 脑子根本就是全是模拟。
反正就是爆了。
正解:
容易发现栈的数量至多为 2(两个栈一定可满足要求)。
尝试先判断栈的数量,发现不好弄。
不妨假设只有一个栈,每次以最优策略转移,若最终栈非空则需要两个栈。
一个栈很好做了,不多赘述。
加入另一个栈如何做?
我们可以将第二个栈当成中转站,对于每一个要取出的元素,将该元素上方的所有元素放到第二个栈中,之后取出该元素,再将刚才取出的元素放回。
发现唯一麻烦的就是求操作次数,设当前要拿出的元素的位置是 \(p\) ,次数 显然为 \(p-1-该元素上方的小于该元素值的元素个数\)(因为该元素上方的小于该元素值的元素个数在此之前已经被弹出了)。
这东西就是一个区间查询和单点加,拿个数据结构维护以下就行(线段树,树状数组什么的)。
code
具体细节看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int T, n;
int a[N], b[N];
int stk[N], top, topb, tot;
struct node
{
int opt, i, j, cnt;
} ans[N << 2];
int c[N];
vector<int> num[N];
#define lowbit(x) (x & (-x))
inline void add(int pos, int val)
{
for (int i = pos; i <= n; i += lowbit(i))
c[i] += val;
}
inline int query(int pos)
{
int ans = 0;
for (int i = pos; i; i -= lowbit(i))
ans += c[i];
return ans;
}
inline void write(int x)
{
cout << x << "\n";
cout << tot << "\n";
for (int i = 1; i <= tot; ++i)
{
if (ans[i].opt == 1)
cout << ans[i].opt << ' ' << ans[i].i << ' ' << ans[i].cnt << "\n";
if (ans[i].opt == 2)
cout << ans[i].opt << ' ' << ans[i].i << "\n";
if (ans[i].opt == 3)
cout << ans[i].opt << ' ' << ans[i].i << ' ' << ans[i].j << ' ' << ans[i].cnt << "\n";
}
}
inline void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i], b[i] = a[i];
sort(b + 1, b + 1 + n);
stk[0] = 0;
tot = top = 0;
topb = 1;
for (int id = n; id; --id)
{
if (a[id] == b[topb])
{
// cerr << "id=" << id << "\n";
ans[++tot] = {1, 1, 114514, 1};
ans[++tot] = {2, 1, 114514, 1};
++topb;
while (top)
{
if (stk[top] != b[topb])
break;
// cerr << "top=" << top << "\n";
ans[++tot] = {2, 1, 114514, 1};
--top, ++topb;
}
}
else
{
ans[++tot] = {1, 1, 114514, 1};
stk[++top] = a[id];
}
}
if (!top)
{
write(1);
return;
}
else
{
vector<int> now;
reverse(stk + 1, stk + top + 1);
// cerr << "top=" << top << "\n";
for (int i = 1; i <= n; ++i)
c[i] = 0;
for (int i = 1; i <= top; ++i)
num[stk[i]].emplace_back(i), now.emplace_back(stk[i]);
now.emplace_back(1e9);
sort(now.begin(), now.end());
for (int i = 1; i < now.size(); ++i)
if (now[i - 1] != now[i])
for (auto p : num[now[i - 1]])
{
int dx = p - 1 - query(p - 1);
if (dx)
ans[++tot] = {3, 1, 2, dx};
ans[++tot] = {2, 1, 0, 0};
if (dx)
ans[++tot] = {3, 2, 1, dx};
add(p, 1);
// cerr << "i=" << i << "\n";
}
// cerr << "\n";
for (int i = 1; i <= top; ++i)
num[stk[i]].clear();
write(2);
}
}
signed main()
{
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
t3
不是你赛场高精???
破防了。
t4
神秘dp。
真不是每个包拆开都成一道题了?
直接看题解吧:
对于每个点,称距离其最近的染色点为配对染色点。
观察到:对于子树 \(u\) 的所有点,其配对染色点至多只有1个在子树外(若有多个可以只留距离 \(u\) 点最近的)。
设计动态规划 \(dp[u][j]\) 代表搞定 \(u\) 子树、且 \(u\) 和 \(j\) 配对的最小花费,记 \(f[i]=\min(dp[i][j])\),答案为 \(f[1]\)。
考虑转移,对于 \(dist(i, j)\leq d[i]\) 的状态,考虑 \(u\) 的每个儿子 \(v\):
- 若 \(v\) 子树的配对集合完全在子树内,贡献为 \(f[v]\);
- 若 \(v\) 子树的配对集合除了 \(j\) 点都在子树内,贡献为 \(f[v]-c[j]\);
- 若 \(v\) 子树的配对集合有在子树外、且不是 \(j\) 的其他点 \(j'\),则不是最优解(子树外多余1个)
转移方程为 \(d p[u][j]=c[j]+\sum \min (f[v], dp[v][j]-c[j])\),复杂度 \(O(tn^2)\)。
本文来自博客园,作者:HS_fu3,转载请注明原文链接:https://www.cnblogs.com/HS-fu3/p/19170043

浙公网安备 33010602011771号