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)\)

posted @ 2025-10-27 20:12  HS_fu3  阅读(25)  评论(0)    收藏  举报