zoukankan      html  css  js  c++  java
  • 1113考试总结

    1113考试总结

    T1

    ​ 题目大意:

    ​ 随机生成一个特殊的 n 个节点的有向图,每个节点有且仅有一条出边,已知生成该图时,每个点的出边指向每个点(包括自己)的概率是相同的, 现在要你求出该有向图的 弱连通分量的期望个数。你只需要给出该期望值以 乘以 (n ^ n) 对 并对 (998244353) 取模的结果即可。 (n <= 1e7)

    ​ 弱连通分量 : 在一张有向图中,将所有有向边变为无向边后, 每一个连通块称为一个弱连通分量。

    ​ 组合数, 期望.

    ​ 这道题已经降低难度了.因为题目说对期望值乘上(n ^ n).可以发现, 一共有(n ^ n)种不同的无向图, 那么每一种图出现的概率就是(frac{1}{n ^ n}).我们知道期望等于权值乘概率, 概率消掉了, 只剩权值.所以题目转化为求弱连通分量的总个数.

    ​ 我们可以发现每一个弱联通分量都是一个基环内向树,也就是说一个弱连通分量有且仅有一个环(无向), 然后问题又转换成了求环的总个数.

    (displaystyle sum_{i = 1}^{n} C_{n}^{i}*(i - 1)!*n^{(n - i)}).

    ​ 解释一下:(i)为当前环的大小,也就是有几个点的环.然后(i)个点的环有((i - 1)!)种排列方式,因为第一个点可以向外伸出(i - 1)条边,第二个点(i - 2)条边.....也就是((i - 1) * (i - 2) * ... * 1 = (i - 1)!).然后剩下(n - i)个点, 每个点可以向外连(n)条.

    #include <bits/stdc++.h>
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 1e7 + 5, mod = 998244353;
    int n, ans;
    int fac[N], inv[N], pow_n[N];
    
    void make_pre() {
    	fac[0] = fac[1] = inv[0] = inv[1] = pow_n[0] = 1;
    	for(int i = 1;i <= N - 5; i++) pow_n[i] = 1ll * pow_n[i - 1] * n % mod;
    	for(int i = 2;i <= N - 5; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
    	for(int i = 2;i <= N - 5; i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
    	for(int i = 2;i <= N - 5; i++) inv[i] = 1ll * inv[i - 1] * inv[i] % mod;
    }
    
    int C(int x, int y) {
    	if(x < y) return 0;
    	return 1ll * fac[x] * inv[y] % mod * inv[x - y] % mod;
    }
    
    int ksm(int x, int y) {
    	int res = 1;
    	while(y) {
    		if(y & 1) res = 1ll * res * x % mod;
    		x = 1ll * x * x % mod; y >>= 1;
    	}
    	return res;
    }
    
    int main() {
    
    	n = read(); make_pre();
    	for(int i = 1;i <= n; i++) ans = (ans + 1ll * C(n, i) * fac[i - 1] % mod * pow_n[n - i] % mod) % mod;
    	printf("%d", ans);
    	
    	return 0;
    }
    

    T2

    ​ 题目大意:

    ​ 有n 个珠宝,编号为 1..n,这个 n 个珠宝被 n-1 条绳子连接了起来,恰好连接成了一棵树的形态。要求恰好留下 k 个珠宝。为了方便取下这 k 个珠宝,同时不使
    这 k 个珠宝失散,他要求留下的 k 个珠宝必须恰好是树形态中的一个连通块。同时使得留下的k 个珠宝的编号的中位数最大。为了方便定义中位数,这里保证 k 为奇数。(n <= 3000)

    ​ 二分 + 树上背包.

    ​ 我们二分中位数(mid).

    (f[x][i])表示以(x)为根的子树内, 节点数为(i)的联通块内, 编号大于等于(mid)的个数.如果说(max(f[x][k]) >= k / 2 + 1)说明当前的(mid)合法, 增大(l),否则减小(r).

    ​ 那么怎么计算(f)数组呢?

    ​ 设(y)(x)的一个子节点, 我们现在要合并(y)的子树与已经与(x)合并过的那些子树的答案.令(h[i + j] = f[x][i] + f[y][j]), 然后(f[x][i] = h[i]).统计完啦!很好理解.

    ​ 此时计算(f)数组的复杂度是(O(n ^ 3))的, 我们可以使枚举上限变成(siz[x])来降低复杂度.题解说可以降低为(O(n ^ 2))我也不知道为啥.

    ​ 总复杂度(O(n ^ 2log n))

    #include <bits/stdc++.h>
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 3005;
    int n, k, cnt, ans, mid, tmp;
    int f[N][N], h[N << 1], siz[N], head[N];
    struct edge { int to, nxt; } e[N << 1];
    
    void add(int x, int y) {
    	e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
    }
    
    void dfs(int x, int fa) {
    	f[x][1] = (x >= mid); siz[x] = 1;
    	for(int i = head[x]; i ; i = e[i].nxt) {
    		int y = e[i].to; if(y == fa) continue;
    		dfs(y, x);
    		for(int j = 1;j <= siz[x] + siz[y]; j++) h[j] = 0;
    		for(int j = 1;j <= siz[x]; j++)
    			for(int k = 0;k <= siz[y]; k++) h[j + k] = max(h[j + k], f[x][j] + f[y][k]);
    		for(int j = 1;j <= siz[x] + siz[y]; j++) f[x][j] = h[j];
    		siz[x] += siz[y];
    	}
    	if(siz[x] >= k) tmp = max(tmp, f[x][k]);
    }
    
    int main() {
    	
    	n = read(); k = read();
    	for(int i = 1, x, y;i < n; i++) 
    		x = read(), y = read(), add(x, y), add(y, x);
    	int l = 1, r = n;
    	while(l < r) {
    		mid = (l + r) >> 1; tmp = 0; dfs(1, 0);
    		if(tmp >= k / 2 + 1) l = mid;
    		else r = mid - 1;
     	}
     	printf("%d", l);
    	
    	return 0;
    }
    

    T3

    ​ 题目大意:

    ​ 迷宫是一个 n 行m 列的网格,起点在第 sx 行第sy 列,终点在第 tx 行第 ty 列。迷宫里的每个格子上有一个数字,第 i 行第 j列的数字记为 a(i,j)。
    ​ 从起点开始,每次可以跳到同一行或者同一列的某个格子,但是这一跳会产生一定的花费,花费的大小为起跳点和落地点之间所有格子(包含这两个格子)上的数字的最小值。求出从起点到终点的最小总花费。(n * m <= 100000).

    ​ 建图 , 最短路.md建图老恶心了

    ​ 我们如果让每个格子和自己同行同列的所有格子都连边的话, 需要建(n * m * (n + m))条边,这肯定是建不出来的.

    ​ 考虑另一种建图方式, 方个图先:

    ​ 方格是原图上的方格, 红蓝点都是虚点.我们按照上图的建图方式, 都是有向边,红边和蓝边边权为0,棕色边边权为跨过格子上的数字.从方格里开始走,必定先走到红点,再走到蓝点,再走到另一个格子里.就相当于是总左往右找了一条路径,使一个格子到另一个格子经过他们之间的最小值.所以从右往左,从上往下,从下往上都是同理,建完图跑一边dij就行了.

    ​ 这么建图点大概有(4nm)个,边大概有(20nm),是可以建出来的.

    #include <bits/stdc++.h>
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 1e6 + 5;
    const long long inf = 1e18;
    int n, m, d, b, s, t, nn, cnt, tag;
    int a[N], f[N], g[N], vis[N], head[N];
    long long dis[N];
    struct edge { int to, nxt, val; } e[7000005 * 2];
    
    int get_id(int x, int y) { return (x - 1) * m + y; }
    
    void add(int x, int y, int z) {
    	e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].val = z;
    }
     
    void run_dij() {
    	priority_queue <pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > q;
    	for(int i = 1;i <= nn; i++) dis[i] = inf;
    	dis[s] = 0; q.push(make_pair(dis[s], s));
    	while(!q.empty()) {
    		int x = q.top().second; q.pop();
    		if(vis[x]) continue; vis[x] = 1;
    		for(int i = head[x]; i ; i = e[i].nxt) {
    			int y = e[i].to;
    			if(dis[y] > dis[x] + e[i].val) 
    				dis[y] = dis[x] + e[i].val, q.push(make_pair(dis[y], y));
    		}
    	}
    }
     
    void work() {
    	nn = get_id(n, m);
    	for(int i = 1;i <= n; i++) {
    		for(int j = 1;j <= m; j++) {
    			int id = get_id(i, j);
    			f[j] = ++ nn; g[j] = ++ nn;
    			add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
    		}
    		for(int j = 1;j < m; j++) add(f[j], f[j + 1], 0), add(g[j], g[j + 1], 0);
    		for(int j = 1;j <= m; j++) {
    			int id = get_id(i, j);
    			f[j] = ++ nn; g[j] = ++ nn;
    			add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
    		}
    		for(int j = 1;j < m; j++) add(f[j + 1], f[j], 0), add(g[j + 1], g[j], 0);
    	}
    	for(int i = 1;i <= m; i++) {
    		for(int j = 1;j <= n; j++) {
    			int id = get_id(j, i);
    			f[j] = ++ nn; g[j] = ++ nn;
    			add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
    		}
    		for(int j = 1;j < n; j++) add(f[j], f[j + 1], 0), add(g[j], g[j + 1], 0);
    		for(int j = 1;j <= n; j++) {
    			int id = get_id(j, i);
    			f[j] = ++ nn; g[j] = ++ nn;
    			add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
    		}
    		for(int j = 1;j < n; j++) add(f[j + 1], f[j], 0), add(g[j + 1], g[j], 0);
    	}
    	run_dij(); printf("%lld", dis[t]);
    }
     
    int main() {
    
    	n = read(); m = read(); 
    	d = read(); b = read(); s = get_id(d, b);
    	d = read(); b = read(); t = get_id(d, b);
    	for(int i = 1;i <= n; i++)
    		for(int j = 1;j <= m; j++) a[get_id(i, j)] = read();
    	work(); 
    
    	return 0;
    }
    

    T4

    ​ 题目大意:

    ​ 约瑟夫游戏的规则是这样的:n 个人围成一圈,从 1 号开始依次报数,当报到 m 时, 报 1、2、...、m-1 的人出局,下一个人接着从 1 开始报,保证(n-1)是(m-1)的倍数。最后剩的一个人获胜。求最后剩下的那一个人的位置.(n <= 2 ^ {64} - 1).

    ​ 递归.

    ​ 设当前有(n) 个人,(m)次.那么第一轮进行完后将会剩下(n / m + n \% m)个人.这里的轮数是指数不超过(n)个人直到不能再数为止.

    ​ 比如: n = 13, m = 4, 第一轮数完就剩4, 8, 12, 13. 然后我们将(n \% m)提前, 13, 4, 8, 12

    ​ 设(solve(n))表示(n)个人围成一圈时的答案.设(tmp = solve(n / m +n \% m)).

    ​ 如果说(tmp <= n \% m)说明最后剩下的那个人一定是在本轮里最后剩下的那几个人里, 所以最后剩下的那个人在当前轮的位置就是(n - tmp + 1).

    ​ 如果说(tmp > n \% m)说明最后剩下的那个人在(n / m)那几个人里, 所以在当前轮的位置就是((tmp - n \% m) * m).

    #include <bits/stdc++.h>
    
    using namespace std;
    
    inline long long read() {
    	long long s = 0, f = 1; char ch;
    	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    	return s * f;
    }
    
    const int N = 1e6 + 5;
    long long n, m;
    
    long long solve(long long n) {
    	if(n == 1) return 1;
    	long long tmp = solve(n / m + n % m);
    	if(tmp <= n % m) return  n - tmp + 1;
    	else return m * (tmp - n % m);
    }
    
    int main() {
    
    	n = read(); m = read();
    	cout << solve(n);
    
    	return 0;
    }
    
  • 相关阅读:
    C#多态联系之虚方法
    FileStream 操作文件复制
    [Android] Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.LoaderCallbacks)
    [Android] Android 异步定时任务实现的三种方法(以SeekBar的进度自动实现为例)
    [Android] Android Butterknife 8.8.1 在 Activity 和 Fragment 、 Adapter 中的使用
    [Android] Android v4包CompoundButtonCompatLollipop.class重复问题
    [Android] Android 常见第三方库汇总地址
    [Android] Android ViewPager 中加载 Fragment的两种方式 方式(二)
    [Android] Android ViewPager 中加载 Fragment的两种方式 方式(一)
    [Android] Android : lambda expressions are not supported at this language level(需设置project language level)
  • 原文地址:https://www.cnblogs.com/czhui666/p/13971653.html
Copyright © 2011-2022 走看看