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;
    }
    
  • 相关阅读:
    Azure PowerShell (7) 使用CSV文件批量设置Virtual Machine Endpoint
    Windows Azure Cloud Service (39) 如何将现有Web应用迁移到Azure PaaS平台
    Azure China (7) 使用WebMetrix将Web Site发布至Azure China
    Microsoft Azure News(4) Azure新D系列虚拟机上线
    Windows Azure Cloud Service (38) 微软IaaS与PaaS比较
    Windows Azure Cloud Service (37) 浅谈Cloud Service
    Azure PowerShell (6) 设置单个Virtual Machine Endpoint
    Azure PowerShell (5) 使用Azure PowerShell创建简单的Azure虚拟机和Linux虚拟机
    功能代码(1)---通过Jquery来处理复选框
    案例1.用Ajax实现用户名的校验
  • 原文地址:https://www.cnblogs.com/czhui666/p/13971653.html
Copyright © 2011-2022 走看看