11.16 L5-NOIP模拟3 题解

11.16 L5-NOIP模拟3 题解

比赛 - 码创未来

A. 串串串(小思维题)

题意

给定两个 01 串 \(s\)\(t\)

进行 \(q\) 次询问,每次询问给定 \(l_1,r_1,l_2,r_2\) 满足 \(r_1-l_1+1=r_2-l_2+1\)

\(a=s[l_1..r_1],b=t[l_2..r_2]\),求出 \(\sum\limits_{i=l_1}^{r_1}[a_i\ne b_i]\bmod2\)

对于 \(100\%\) 的数据,\(1\le n,m,q\le2\times10^5,1\le l_1\le r_1\le n,1\le l_2\le r_2\le m\)

思路

这道题的关键点在于对 \(2\) 取余。

对于每一位,有四种情况:00, 11, 01, 10。

前两种情况对答案的贡献是 \(0\),后两种情况对答案的贡献是 \(1\)

我们发现,每种情况的 1 的个数对 \(2\) 取余正好等于对答案的贡献。

于是计算 \(a\)\(b\) 中的 1 的总数对 \(2\) 取余即为答案。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define FILENAME "string"
using namespace std;
const int N = 2e5 + 10;
int n, m, q, s1[N], s2[N], l1, l2, r1, r2;
char s[N], t[N];

signed main() {
    // ios::sync_with_stdio(false);
    // cin.tie(0), cout.tie(0); //不要cin cout解绑并且混用cin cout和scanf printf!!!!!
    freopen(FILENAME".in", "r", stdin);
    freopen(FILENAME".out", "w", stdout);
    
    scanf("%d%d", &n, &m);
    scanf("%s%s", s + 1, t + 1);
    f(i, 1, n) s1[i] = s1[i - 1] + (s[i] == '1');
    f(i, 1, m) s2[i] = s2[i - 1] + (t[i] == '1');
    cin >> q;
    while (q--) {
        cin >> l1 >> r1 >> l2 >> r2;
        cout << ((s1[r1] - s1[l1-1] + s2[r2] - s2[l2-1]) & 1) << '\n';
    }
    
    return 0;
}

B. 方格计数(组合数学)

题意

在左下角是 \((0,0)\),右上角是 \((W,H)\) 的网格上,有 \((W+1)\times(H+1)\) 个格点。

现在要在格点上找 \(N\) 个不同的点,使得这些点在一条直线上。并且在这条直线上,相邻点之间的距离不小于 \(D\)

给定 \(N,W,H,D\),求方案数模 \(10^9+7\)\(T\) 组数据。

对于 \(100\%\) 的数据,\(1\le N\le50,1\le W,H,D\le500,1\le T\le20\)

思路

先来考虑一条线段上的情况。我们令这条线段的两个端点分别为 \((x_1,y_1)\)\((x_2,y_2)\)

\(x=|x_1-x_2|,y=|y_1-y_2|\)。容易发现,线段上的格点数为 \(\gcd(x,y)+1\)

考虑相邻点之间的距离不小于 \(D\) 的限制。

首先考虑一个简化的问题:一条直线上有 \(n\) 个点,每个点之间距离为 \(1\),要求选出 \(m\) 个点,且这些点之间的点数不小于 \(d\),求方案数。

这个问题可以用隔板法解决。有 \(m\) 个隔板,在两两隔板之间先放 \(d\) 个点,然后再在剩余的点中选出 \(m\) 个放在隔板处,其余的点放在两两隔板之间。那么方案数为 \(\dbinom{n-d(m-1)}m\)

回到那条线段上来。设选择的两个点之间至少要有 \(d\) 个点,那么 \(d=\left\lceil\dfrac{D}{l}\right\rceil-1\),其中两点间距离 \(l=\dfrac{\sqrt{x^2+y^2}}{\gcd(x,y)}\)。由于两个端点是一定要选的,所以两个端点以及端点旁边的 \(d\times2\) 个点不能再选,所以相当于是从 \(\gcd(x,y)+1-2d-2\) 个点中选出 \(n-2\) 个点,且选出的点两两之间点数不小于 \(d\),那么方案数为

\[\binom{\gcd(x,y)-2d-1-d(n-2-1)}{n-2}=\binom{\gcd(x,y)-dn+d-1}{n-2}. \]

现在我们知道了一条线段的答案。如何求所有线段的答案?

注意到对于一条线段,结果只与 \(x,y,n,D\) 有关。所以我们枚举 \(x,y\) 算出答案,然后乘以 \(x,y\) 相同的线段的条数,即 \((W+1-x)\times(H+1-y)\)(稍微模拟一下不难得出)。

还有一个细节:由于 \(x,y\) 是绝对值,所以如果 \(x\ne0\)\(y\ne0\),那么答案要乘以 \(2\)

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
#define FILENAME "num"
using namespace std;
typedef long long ll;
const int N = 550;
const int MOD = 1e9 + 7;
int n, w, h, d, tt, g, k, t;
ll ans;
double l;

inline double sqrt(double x) { return __builtin_sqrt(x); }
inline double ceil(double x) { return __builtin_ceil(x); }

int C[N][N];
void getC() {
    f(i, 0, 500) {
        C[i][0] = 1;
        f(j, 1, i) C[i][j] = (1ll * C[i-1][j] + C[i-1][j-1]) % MOD;
    }
    return;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    freopen(FILENAME".in", "r", stdin);
    freopen(FILENAME".out", "w", stdout);
    
    getC();
    cin >> tt;
    while (tt--) {
        cin >> n >> w >> h >> d;
        if (n == 1) {
            cout << (w + 1) * (h + 1) << '\n';
            continue;
        }
        ans = 0;
        f(x, 0, w) f(y, 0, h) {
            if (x == 0 && y == 0) continue;
            g = __gcd(x, y);
            l = sqrt(x * x + y * y) / g;
            k = ceil(d / l) - 1;
            t = g - k * n + k - 1;
            if  (t < 0) continue;
            ans += C[t][n - 2] * 1ll * (w + 1 - x) % MOD * (h + 1 - y) % MOD * ((x == 0 || y == 0) ? 1 : 2) % MOD;
            ans %= MOD;
        }
        cout << ans << '\n';
    }
    
    return 0;
}

C. 树数树(启发式合并堆)

题意

有一棵 \(n\) 个点的有根树,根为 \(1\)。 我们称一个长度为 \(m\) 的序列 \(a\) 符合条件,当且仅当:

  • \(\forall\ i\in(1,m]\)\(a_i\)\(a_{i−1}\)祖先\(a_{i−1}\)\(a_i\)祖先(注意不是父亲);
  • \(\forall\ 1\le i<j\le m\)\(a_i\ne a_j\)

求出最长的符合条件的序列长度。

对于 \(100\%\) 的数据,\(1\le T\le5,2\le n\le10^5,1\le u,v\le n,u\ne v\)。输入保证是一棵树。

思路

由于序列中的点可以从子树到祖先、从祖先到子树,但是不能重复经过,所以对于一个节点 \(u\),它可以把某个子树(不一定是第一代儿子)中的一段序列和另一个子树中的一段序列连接在一起。于是我们使用递归分治合并的做法。

由于要求最长的序列长度,所以要把当前所有的子树中的序列都合并到一个大根堆中(代码中用 std::priority_queue 实现,把小堆合并到大堆中),然后在更新的时候取出最大的两个,把它们连接成一个序列,存到堆中。

树形 DP 不行的原因就在于当前最大的两个序列所在的子树不一定是第一代儿子,所以不能直接用儿子来更新。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define FILENAME "tree"
using namespace std;
const int N = 1e5 + 1000;
int tt, n;

struct Edge {
	int to, nxt;
} e[N << 1];
int head[N], cnt;
inline void add(int from, int to) {
	e[++cnt].to = to, e[cnt].nxt = head[from], head[from] = cnt;
	return;
}

priority_queue<int> q[N];
int merge(int x, int y) {
	if (q[x].size() < q[y].size()) swap(x, y);
	while (!q[y].empty()) q[x].push(q[y].top()), q[y].pop();
	return x;
}

int id[N];
void dfs(int u, int fa) {
	id[u] = u;
	while (!q[u].empty()) q[u].pop();
	for (int i = head[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if (v == fa) continue;
		dfs(v, u);
		id[u] = merge(id[u], id[v]);
	}
	int sum = 1;
	f(i, 1, 2)
		if (q[id[u]].empty()) break;
		else sum += q[id[u]].top(), q[id[u]].pop();
	return q[id[u]].push(sum), void();
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	freopen(FILENAME".in", "r", stdin);
	freopen(FILENAME".out", "w", stdout);
	
	cin >> tt;
	while (tt--) {
		memset(head, 0, sizeof head);
		cnt = 0; //注意将图重置
		cin >> n;
		f(i, 1, n - 1) {
			int u, v;
			cin >> u >> v;
			add(u, v), add(v, u);
		}
		dfs(1, -1);
		cout << q[id[1]].top() << '\n';
	}
	
	return 0;
}

D. 序列(数位 DP,线性代数)

我不会。

题意

定义一个数的 se 序列为其一个数位和为 \(10\) 的子段。

举个例子,1145141919810900 的所有 se 序列为:

  • 145;
  • 451;
  • 514;
  • 19;
  • 91;
  • 19;
  • 109;
  • 1090;
  • 10900。

定义一个数是 ll 数,当且仅当它的每一个数位都在至少一个 se 序列中。

举个例子,1145141919810900 不是 ll 数,因为第一个 1 和 8 不在任何一个 se 序列中,而 23541901 是一个 ll 数。

现在牛牛想随机生成一个 \([0,10^n)\) 范围内的数送给牛妹。具体地说,每一位上的数字为 \(i\) 的概率为 \(a_i\) ,且保证 \(\sum\limits_{i=0}^{9}a_i=1\)

现在牛牛想知道这个数为 ll 数的概率。

对于 \(5\%\) 的数据,\(n=1\)

对于 \(5\%\) 的数据,\(n=100\)

对于 \(20\%\) 的数据,\(n=3000\)

对于另 \(30\%\) 的数据,\(n\le10^{18}\),且保证 \(\forall i\in[0,9],a_i=\dfrac1{10}\)

对于 \(100\%\) 的数据,\(1\le n\le10^{18}\)

posted @ 2022-11-22 11:46  f2021ljh  阅读(59)  评论(0)    收藏  举报