A.直角三棱锥
(数学-等差数列)
题意:在三维空间中,平面 x = 0, y = 0, z = 0,以及平面 x + y + z = K 围成了一个三棱锥,告诉你K的值,要求这个三棱锥内、上整点的数目%M的值.
题解:画图可以知道这个三棱锥的四个顶点是(0,0,0)、(0,0,k)、(0,k,0)、(k,0,0)
那么首先我们以平面的单位来数点吧,对于平面z=0,可以看出有(1+2+...+k)个点,等差数列求和得,对于平面z=1,你会发现比平面z=0少了k个点,同理直到z=k时只有一个点,那么总的点数就是对于这个数我们不好直接得出,因为不是正常的等差数列,只好将变换为
,那么现在答案变为,又,因此最终化简出的答案为,剩下的就是考虑奇数偶数整除和取模的问题了(这部分看代码可以懂)。
代码如下:
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<cmath> #include<vector> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define ll long long int main() { int t; scanf("%d", &t); while (t--) { ll k, mod; scanf("%lld%lld", &k, &mod); ll ans; if (k % 2 == 0) { if ((k + 3) % 3 == 0) ans = (((k + 2) / 2)*((k + 3) / 3) % mod)*(k + 1) % mod; else if((k + 1) % 3 == 0) ans = (((k + 2) / 2)*((k + 1) / 3) % mod)*(k + 3) % mod; else ans = (((k + 2) / 6)*(k + 1) % mod)*(k + 3) % mod; } else { if ((k + 1) % 3 == 0) ans = (((k + 3) / 2)*((k + 1) / 3) % mod)*(k + 2) % mod; else if ((k + 2) % 3 == 0) ans = (((k + 3) / 2)*((k + 2) / 3) % mod)*(k + 1) % mod; else ans = (((k + 1) / 2)*((k + 3) / 3) % mod)*(k + 2) % mod; } printf("%lld\n", ans); } return 0; }
B.前缀查询
(模拟+字典树+lazy)
题意:对一个发展中的村庄的人编写名册,输入一个n,表示n步操作。4种操作如下:
1. 插入新人名 s,声望为 x
2. 给定名字前缀 p的所有人的声望值变化 x
3. 查询名字为 s 村民们的声望值的和(因为会有重名的)
4. 查询名字前缀为 p的声望值的和
题解:由于要保存的数据量大,需要使用字典树来对所有村民的名字进行存储,同时为了防止每次进行操作2都要跑整棵字典树会TLE,所有需要对字典树中的每个节点保存一些数据,以达到进行操作4时可以便利完p时,在最后一个节点可以快速读取到需要的前缀和数据sum,同时为了防止题目疯狂进行操作2,所以我们可以在每个点存一个lzay值,方便之后如果访问到该节点就将这个值传递到它的下一个节点,没有访问到就不遍历了,最需要注意的几点是:1.插入一个人时,也需要对遍历到的点lazy!=0进行updata操作;2.对于询问时若是遍历到该人不存在或者以该字符串为前缀的人不存在,都是直接输出0;(剩下的做法具体参照代码了)
代码如下:
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<cmath> #include<vector> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn = 1e5 + 500; //模拟+字典树+lazy struct Trie { struct node {//注意用long long ll cnt; //统计从"初始节点到该节点"为前缀的人数 ll sum;//统计从"初始节点到该节点"为前缀的人的声望总和 ll lazy;//记录从"初始节点到该节点"为前缀的人的声望变化 ll vail;//记录从"初始节点到该节点"为名字的人的声望总和 ll man;//记录从"初始节点到该节点"为名字的人数 int nxt[26]; void init() { cnt = man = sum = vail = lazy = 0; for (int i = 0; i < 26; i++) nxt[i] = -1; } }; node tree[(int)3e6]; int top; void init() { top = 1; tree[0].init(); } void updata(int p) { tree[p].sum += tree[p].lazy*tree[p].cnt; tree[p].vail += tree[p].lazy*tree[p].man; if (tree[p].lazy != 0) {//lazy的传递 for (int i = 0; i < 26; i++) if (tree[p].nxt[i] != -1) tree[tree[p].nxt[i]].lazy += tree[p].lazy; tree[p].lazy = 0; } } void insert(char *s, int len, ll num) {//1 int p = 0; for (int i = 0; i < len; i++) { if (tree[p].lazy != 0) updata(p);//这里也要注意更新,因为插入的人会影响cnt等数据导致updata的数据偏离 tree[p].cnt++; tree[p].sum += num; if (tree[p].nxt[s[i] - 'a'] == -1) { tree[p].nxt[s[i] - 'a'] = top; tree[top].init(); top++; } p = tree[p].nxt[s[i] - 'a']; } if (tree[p].lazy != 0) updata(p); tree[p].cnt++; tree[p].sum += num; tree[p].vail += num; tree[p].man++; } void add(char *s, int len, ll num) {//2 int p = 0; for (int i = 0; i < len; i++) { if (tree[p].nxt[s[i] - 'a'] == -1)return; p = tree[p].nxt[s[i] - 'a']; } tree[p].lazy += num; num = tree[p].cnt*num;//(注意:要再跑一遍,因为当后面有lazy更新后前面的前缀sum值也需要更新) p = 0; for (int i = 0; i < len; i++) { tree[p].sum += num; p = tree[p].nxt[s[i] - 'a']; } } ll find(char *s, int len,int op) {//k=3或4 int p = 0; for (int i = 0; i < len; i++) { if (tree[p].nxt[s[i] - 'a'] == -1)return 0; if (tree[p].lazy != 0) updata(p); p = tree[p].nxt[s[i] - 'a']; } if (tree[p].lazy != 0) updata(p); if (op == 3) return tree[p].vail; else return tree[p].sum; } }T; char s[maxn]; int main() { T.init(); int n; scanf("%d", &n); while (n--){ int op;ll x; scanf("%d", &op); if (op == 1) { scanf("%s%lld", &s, &x); T.insert(s, strlen(s), x); } else if (op == 2) { scanf("%s%lld", &s, &x); T.add(s, strlen(s), x); } else if (op == 3) { scanf("%s", &s); printf("%lld\n", T.find(s, strlen(s), 3)); } else { scanf("%s", &s); printf("%lld\n", T.find(s, strlen(s), 4)); } } return 0; }
C.可达性
(并查集+贪心)
题意:给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
题解:对于这个要求最少的点,我们首先想到是选择入度为0的点,随后再对剩下的点进行覆盖操作(并查集),选择剩下的点进行dfs遍历即可,最后对比每个节点的根节点是否为本身,若是就说明它是最小点覆盖需要用到的点,不是就是被其他点覆盖了,存下那些根为本身的点输出即可。
代码如下:
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<cmath> #include<vector> #include<queue> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn = 1e5 + 500; vector<int>g[maxn]; int root[maxn]; int ans[maxn]; int in[maxn]; int n, m, u, v; int top; void dfs(int u,int rt) { if (root[u] == rt)return; root[u] = rt; for (int i = 0; i < g[u].size(); i++) { v = g[u][i]; dfs(v, rt); } } int main() { while (~scanf("%d%d", &n, &m)){ for (int i = 1; i <= n; i++) {//初始化 g[i].clear(); in[i] = 0; root[i] = 0; } top = 0; while (m--){//添边 scanf("%d%d", &u, &v); g[u].push_back(v); in[v]++; } for (int i = 1; i <= n; i++) //以入度为0的为起点进行dfs访问(当前最优) if (!in[i]) dfs(i, i); for (int i = 1; i <= n; i++) //剩下的就是成环或单点或相互联通的集合 if (!root[i])dfs(i, i); for (int i = 1; i <= n; i++) if (root[i] == i) ans[top++] = i; cout << top << endl; for (int i = 0; i < top - 1; i++) printf("%d ", ans[i]); printf("%d\n", ans[top-1]); } return 0; }
D.codeJan和树
待补~
E.无效位置
(线性基+并查集)
题意:题目给出n个数a[i],随后输出n个数表示要第i次操作要删掉的a[]中的第p[i]个数。问你在删去前剩下的数的连续区间中数的异或值的最大值是多少。eg:第一次删去第x个数,则这n个数的区间被分为[1, x -1 ]和[ x+1,n ] 两个区间,那么第二次删除前的得到的答案就是这2个区间分别选取任意个数得到的异或的最大值max(q1,q2),同理若是之后被分为k个区间也是要得到max(q1,q2,…,qk)。
题解:我们可以发过来获取每次要删调的数的位置,然后向该位置添加数即可,若它左右存在数,则与那些进行合并求解最大区间已异或值,至于这个值我们采用线性基来完成。
代码如下:
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<cmath> #include<vector> #include<queue> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn = 1e5 + 500; struct DSU { int b[31], val[maxn][31];//若是long long 全改为62 int prt[maxn]; int find(int x) { return x == prt[x] ? x : prt[x] = find(prt[x]); } void init(int n) { for (int i = 1; i <= n; i++) prt[i] = 0; } void insert(int b[], int x) { for (int i = 30; i >= 0; --i)if ((x >> i & 1)) { if (!b[i]) { b[i] = x; break; } else x ^= b[i]; } } int query_max(int b[]) {//查询最大值 int ans = 0; for (int i = 30; i >= 0; --i)if ((ans^b[i]) > ans)ans ^= b[i]; return ans; } void merge(int x, int y) {//合并 x = find(x), y = find(y); for (int i = 0; i <= 30; ++i)if (val[x][i])insert(val[y], val[x][i]); prt[x] = y; } //下面的2个是模板,该题没用到(而且暂时没用过hhh) int query_min(int b[]) {//查询最小值 for (int i = 0; i <= 30; i++) if (b[i]) return b[i]; return 0; } //查询第k小值 int cnt = 0, p[maxn]; void rebuild() { for (int i = 30; i >= 0; i--) for (int j = i - 1; j >= 0; j--) if (b[i] & (1LL << j)) b[i] ^= b[j]; for (int i = 0; i <= 30; i++) if (b[i]) p[cnt++] = b[i]; } int kthquery(int k) { int ret = 0; if (k >= (1LL << cnt)) return -1; for (int i = 30; i >= 0; i--) if (k&(1LL << i)) ret ^= p[i]; return ret; } }B; int a[maxn]; int p[maxn]; int ans[maxn]; int main() { int n, res = 0; scanf("%d", &n); B.init(n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= n; i++) scanf("%d", &p[i]); for (int i = n; i >=1; i--) { int x = p[i]; B.prt[x] = x, B.insert(B.val[x],a[x]); if (B.prt[x - 1])B.merge(x, x - 1); if (B.prt[x + 1])B.merge(x, x + 1); res = max(res, B.query_max(B.val[B.find(x)])); ans[i] = res; } for (int i = 1; i <= n; i++) printf("%d\n", ans[i]); return 0; }F.细胞
待部~