【牛客网】Wannafly挑战赛14 题解

比赛链接

A 直角三棱锥

题目大意:看题

这里有两种思路,一种是从几何上,一种是代数上

首先几何的解法。

由题可知在xy平面上,这个三棱锥的截面永远是等腰直角三角形,当z=0,k=5时点的分布如下图


由此可知对于截面来说如果其直角边长为k,其截面所包含的点个数为1~k+1的前k+1项和

然后再对这个公式求剩下一个坐标的求和(或者积分)就行了

这里直接给出公式

第二种代数法

由题可知我们求的是x+y+z<=k的正整数解

这样我们可以把它转化为x+y+z+w=k的正整数解

这里我们可以用隔板法进行求解,因为0也算作为答案,因此实际为在k+4个球中加入3块隔板

即C(k+3,3),读者可以验证和上面公式是一样的。

然后就是取模问题

首先因为这是一个可以整除的问题,因此我们可以把6拆分成2*3,然后用(k+1),(k+2),(k+3)对2和3试除达到消去6

当然也可以利用公式 (a/b)%c=a%(b*c)/b解决

注意一下因为6不是质数,所以大多数逆元的求法都不适用。

#include<iostream> 
#include<string> 
#include<cstring> 
#include<vector> 
#include<map> 
#include<algorithm> 
#include<queue> 
#include<set> 
#include<cstdio> 
#include<functional> 
#include<iomanip> 
#include<cmath> 
#include<stack> 
#include<iomanip>
#include<functional>
using namespace std;
const int maxn = 2*(int)(1e5) + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-6;
typedef long long LL;
typedef unsigned long long ull;
int main() {
	int t;
	scanf("%d", &t);
	LL n, mods;
	while (t--) {
	scanf("%lld%lld", &n, &mods);
		if (n & 1) {
			LL a = (n + 1) / 2, b = n + 2, c = n + 3;
			if (a % 3 == 0)
				a /= 3;
			else if (b % 3 == 0)
				b /= 3;
			else if (c % 3 == 0)
				c /= 3;
			LL ans = a%mods*b%mods*c%mods;
			printf("%lld\n", ans);
		}
		else {
			LL a = n + 1, b = (n + 2) / 2, c = n + 3;
			if (a % 3 == 0)
				a /= 3;
			else if (b % 3 == 0)
				b /= 3;
			else if (c % 3 == 0)
				c /= 3;
			LL ans = a%mods*b%mods*c%mods;
			printf("%lld\n", ans);
		}
	}
	return 0;
}
B 前缀查询

题意:还是看题吧

暴力必炸别想了,所以考虑怎么优化吧,首先因为是字符串而且要匹配所以想到用字典树,

又因为3的操作如果实际进行下去会使字典树退化,因此可以考虑用线段树区间求和的优化lazy数组保留,等到要用的时候再修改。

然后就是存法,这里笔者的存法是对于每一个节点,有

以这个节点为末尾的人数(cnt1)以及权值和(sum1)

以这个节点为前缀的人数(cnt2)以及权值和(sum2)

和lazy数组

然后就是字典树的问题了。

具体看代码吧

#include<iostream> 
#include<string> 
#include<cstring> 
#include<vector> 
#include<map> 
#include<algorithm> 
#include<queue> 
#include<set> 
#include<cstdio> 
#include<functional> 
#include<iomanip> 
#include<cmath> 
#include<stack> 
#include<iomanip>
#include<functional>
using namespace std;
const int maxn = 2 * (int)(1e5) + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-6;
typedef long long LL;
typedef unsigned long long ull;

struct TRIE {
	struct nodes {
		int nexts[27];
		LL cnt1, cnt2;//1前缀,2末尾
		LL sum1, sum2;
		LL lazy;
	}trie[maxn];
	void buildnode(int rt) {
		trie[rt].cnt1 = trie[rt].cnt2 = trie[rt].sum1 = trie[rt].sum2 = 0;
		for (int i = 0; i < 27; i++)
			trie[rt].nexts[i] = -1;
	}
	int trie_node;
	void init() {
		trie_node = 1;
		buildnode(0);
	}
	int change(char c) {
		return c - 'a';
	}
	void update(int rt) {
		trie[rt].sum1 += trie[rt].lazy*trie[rt].cnt1;
		trie[rt].sum2 += trie[rt].lazy*trie[rt].cnt2;
		if (trie[rt].lazy != 0) {
			for (int i = 0; i < 26; i++)
				trie[trie[rt].nexts[i]].lazy += trie[rt].lazy;
		}
		trie[rt].lazy = 0;
	}
	void inserts(char *s,LL val) {
		int len = strlen(s), i, rt;
		for (i = rt = 0; i < len; i++) {
			if (trie[rt].lazy != 0)
				update(rt);
			trie[rt].cnt1++;
			trie[rt].sum1 += val;
			int c = change(s[i]);
			if (trie[rt].nexts[c]==-1) {
				buildnode(trie_node);
				trie[rt].nexts[c] = trie_node++;
			}	
			rt = trie[rt].nexts[c];
		}
		if (trie[rt].lazy != 0)
			update(rt);
		trie[rt].cnt1++;
		trie[rt].sum1 += val;
		trie[rt].cnt2++;
		trie[rt].sum2 += val;
	}
	void add(char *s, LL val) {
		int len = strlen(s), i, rt;
		for (i = rt = 0; i < len; i++) {
			int c = change(s[i]);
			if (trie[rt].nexts[c] == -1) return;
			rt = trie[rt].nexts[c];
		}
		trie[rt].lazy += val;
		val = trie[rt].cnt1*val;
		rt = 0;
		for (int i = 0; i < len; i++){
			trie[rt].sum1 += val;
			rt = trie[rt].nexts[change(s[i])];
		}
	}
	LL query(char *s,int op) {
		int len = strlen(s), i, rt;
		for (i = rt = 0; i < len; i++) {
			int c = change(s[i]);
			if (trie[rt].nexts[c] == -1) return 0;
			if (trie[rt].lazy != 0) update(rt);
			rt = trie[rt].nexts[c];
		}
		if (trie[rt].lazy != 0) update(rt);
		if (op == 3) return trie[rt].sum2;
		return trie[rt].sum1;
	}
}trees;
char str[maxn];
int main() {
	//freopen("E:\\test.txt", "r", stdin);
	int n;
	while (~scanf("%d", &n)) {
		trees.init();
		int op;
		LL val;
		for (int i = 0; i < n; i++) {
			scanf("%d%s", &op, str);
			if (op == 1) {
				scanf("%lld", &val);
				trees.inserts(str, val);
			}
			else if (op == 2) {
				scanf("%lld", &val);
				trees.add(str, val);
			}
			else
				printf("%lld\n", trees.query(str, op));
		}
	}
	return 0;
}
C 可达性

题意:应该不会有异义的吧。。

明显是强连通缩点的模板题,缩点后把入度为0的点输出就行了。

#include<iostream> 
#include<string> 
#include<cstring> 
#include<vector> 
#include<map> 
#include<algorithm> 
#include<queue> 
#include<set> 
#include<cstdio> 
#include<functional> 
#include<iomanip> 
#include<cmath> 
#include<stack> 
#include<iomanip>
#include<functional>
using namespace std;
const int maxn = 2 * (int)(1e5) + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-6;
typedef long long LL;
typedef unsigned long long ull;
struct tarjan {
	int dfn[maxn];//该节点被搜索的次序(时间戳)
	int low[maxn];//为i或i的子树能够追溯到的最早的栈中节点的次序号
	int stk[maxn];//栈模拟数组
	int col[maxn];//强联通分量染色
	int in[maxn];
	bool vis[maxn];//是否在栈内
	bool viss[maxn];
	vector<int>edge[maxn];//保存边
	vector<int>ans;
	int tot, coln, tops, n;//搜索次序,染色总数,栈顶,点数
	void init(int _n) {
		n = _n;
		for (int i = 1; i <= n; i++) {
			dfn[i] = -1;
			col[i] = in[i] = low[i] = 0;
			edge[i].clear();
		}
		tot = coln = tops = 0;
		ans.clear();
	}
	void addedge(int u, int v) {
		edge[u].push_back(v);
	}
	void dfs(int u) {
		dfn[u] = low[u] = ++tot;
		stk[++tops] = u;
		vis[u] = true;
		for (int i = 0; i < edge[u].size(); i++) {
			int v = edge[u][i];
			if (dfn[v] == -1) {
				dfs(v);
				low[u] = min(low[u], low[v]);
			}
			else if (vis[v])
				low[u] = min(low[u], dfn[v]);
		}
		if (dfn[u] == low[u]) {
			int v;
			coln++;
			do {
				v = stk[tops--];
				vis[v] = false;
				col[v] = coln;
			} while (v != u);
		}
	}
	void solve() {
		for (int i = 1; i <= n; i++)
			if (dfn[i] == -1)
				dfs(i);
	}
	void dag() {
		memset(viss, 0, sizeof(viss));
		for (int u = 1; u <= n; u++) {
			for (int i = 0; i < edge[u].size(); i++) {
				int v = edge[u][i];
				int x = col[u], y = col[v];
				if (x != y) {
					in[y]++;
				}
			}
		}
		for (int i = 1; i <= n; i++)
			if (in[col[i]] == 0&&!viss[col[i]]) {
				viss[col[i]] = 1;
				ans.push_back(i);
			}
				
		int len = ans.size();
		printf("%d\n", len);
		for (int i = 0; i < len; i++) {
			printf("%d", ans[i]);
			printf("%s", i < len - 1 ? " " : "\n");
		}
	}
}tar;
int main() {
	int n, m;
	while (~scanf("%d%d", &n, &m)) {
		tar.init(n);
		int u, v;
		for (int i = 0; i < m; i++) {
			scanf("%d%d", &u, &v);
			tar.addedge(u, v);
		}
		tar.solve();
		tar.dag();
	}
	return 0;
}
D codeJan和树

题意:咕咕咕

首先预处理出所有节点的权值,

由于题目要求的是a-b<=m中a-b的最大值,这个不等式可以转化为a<=b+m。这样我们可以用刚才预处理出的权值表用二分查找找出最大的对于b+m来说最大的a。

这里难点是如何预处理所有节点的权值。

首先根据题意可知,对于一颗子树来说,其根节点和某一个和它相连节点的边被加的次数和相连节点的大小成正比(有点拗口)

如下图


对于以B为根节点的子树而言,BC连边被加的次数就是C为根的子树大小(读者可以对着这颗树画一下)。

所以我们可以用两个数组,一个保存子树大小,一个保存权值,然后就是用上述的方法遍历打表就行了。

#include<iostream> 
#include<string> 
#include<cstring> 
#include<vector> 
#include<map> 
#include<algorithm> 
#include<queue> 
#include<set> 
#include<cstdio> 
#include<functional> 
#include<iomanip> 
#include<cmath> 
#include<stack> 
#include<iomanip>
#include<functional>
using namespace std;
const int maxn = (int)(1e5) + 10;
const int BN = 30;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-6;
typedef long long LL;
typedef unsigned long long ull;
struct nodes {
	int v, w;
	nodes() {}
	nodes(int _v, int _w) :v(_v), w(_w) {}
};
vector<nodes>edge[maxn * 2];
int n;
LL m, ans;
int sz[maxn];
LL dis[maxn];
void dfs1(int u, int pre) {
	sz[u] = 1, dis[u] = 0;
	for (int i = 0; i < edge[u].size(); i++) {
		int v = edge[u][i].v;
		if (v == pre) continue;
		dfs1(v, u);
		dis[u] += dis[v] + LL(sz[v])*LL(edge[u][i].w);
		sz[u] += sz[v];
	}
}
set<LL> se;
void dfs2(int u, int pre) {
	set<LL>::iterator it = se.upper_bound(m + dis[u]);
	if (it != se.begin()) {
		--it;
		ans = max(ans, (*it) - dis[u]);
	}
	se.insert(dis[u]);
	for (int i = 0; i < edge[u].size(); i++) {
		int v = edge[u][i].v;
		if (v == pre) continue;
		dfs2(v, u);
	}
	se.erase(se.find(dis[u]));
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%lld", &n, &m);
		for (int i = 1; i <= n; i++)
			edge[i].clear();
		int u, v, w;
		for (int i = 0; i < n-1; i++) {
			scanf("%d%d%d", &u, &v, &w);
			edge[u].push_back(nodes(v, w));
			edge[v].push_back(nodes(u, w));
		}
		ans = -1;
		dfs1(1,-1);
		dfs2(1,-1);
		printf("%lld\n", ans);
	}
	return 0;
}
E 无效位置

题意:这次读一下吧(因为笔者和队友都读错题了)

定义区间的权值为区间内数的子序列异或和最大值(非连续)

给出n个数,每次操作给出一个下表,表示去掉这个下标的数。

问在操作之前区间权值的最大值是多少。

这里笔者读错了一点就是删除数字后区间是会分开的

拿样例来说

10
169 816 709 896 58 490 97 254 99 796 

4 2 3 10 5 6 1 8 9 7

第一次删除第四个数,区间变成[1,3]和[5,10],当下次操作的时候求的答案是这两个区间权值中最大的那个

笔者读错为删除之后区间合并成一个(傻了)


首先这是一道模板题(摔)。

这里的做法是线性基+并查集,线性基请读者自行百度(因为笔者也不大会)

并查集的作用是快速合并区间

为什么是合并呢,显然这里删除的操作可以逆过来考虑为添加的操作。

如果是添加的操作的话合并区间明显用并查集可以加速优化

具体操作参考代码

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<map>
#include<algorithm>
#include<queue>
#include<set>
#include<cstdio>
#include<functional>
#include<iomanip>
#include<cmath>
#include<stack>
#include<iomanip>
#include<functional>
using namespace std;
const int maxn = 2 * (int)(1e5) + 10;
const int BN = 30;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-6;
typedef long long LL;
typedef unsigned long long ull;
int par[maxn], num[maxn];
int pos[maxn], ans[maxn];
int find(int x) {
	return x == par[x] ? x : par[x] = find(par[x]);
}
struct linearbases {
	int d[BN + 1];
	void init() {
		memset(d, 0, sizeof(d));
	}
	void insert(int val) {
		for (int i = BN; i >= 0; i--) {
			if (val&(1 << i)) {
				if (!d[i]) {
					d[i] = val;
					break;
				}
				val ^= d[i];
			}
		}
	}
	int query_max() {
		int ans = 0;
		for (int i = BN; i >= 0; i--)
			ans = max(ans, ans ^ d[i]);
		return ans;
	}
	void merge(linearbases& n) {
		for (int i = BN; i >= 0; i--) {
			if (n.d[i])
				insert(n.d[i]);
		}
	}
}bases[maxn];
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &num[i]);
	for (int i = 1; i <= n; i++)
		scanf("%d", &pos[i]);
	for (int now = 0, i = n; i >= 1; i--) {
		par[pos[i]] = pos[i];
		bases[pos[i]].insert(num[pos[i]]);
		if (par[pos[i] - 1]) {
			int tmp = find(pos[i] - 1);
			bases[tmp].merge(bases[pos[i]]);
			par[pos[i]] = tmp;
		}
		if (par[pos[i] + 1]) {
			int tmp1 = find(pos[i] + 1);
			int tmp2 = find(pos[i]);
			bases[tmp1].merge(bases[tmp2]);
			par[tmp2] = tmp1;
		}
		now = max(now, bases[find(pos[i])].query_max());
		ans[i] = now;
	}
	for (int i = 1; i <= n; i++)
		printf("%d\n", ans[i]);
	return 0;
}
没有F题哦

猜你喜欢

转载自blog.csdn.net/xiuya19/article/details/80032085