2018年第十届ACMICPC四川省大学程序设计竞赛
..拿金了 没给学校丢脸
A
....SB题啊 比赛的时候都没看 裸的一个bitset前缀和
先开一个1e4*1e4的二维bitset数组 初始第i个数组的值为1 << i (即B[i]=1 B[i]<<=i)
很容易我们可以知道要单独翻转某一位而不去影响其他位的话 方法是唯一的
然后我们从可以后往前DP 就可以知道要单独翻转某一位的话需要翻转那些位
最后的每次翻转过后的答案就是 上一个答案^PreL-1 ^PreR
注意用cout的话容易System Error......(再喷一次OJ
/*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; typedef unsigned long long ull; const int dir[8][2] = {{0, 1}, {1, 0}, {0, -1}, { -1, 0}, {1, 1}, {1, -1}, { -1, -1}, { -1, 1}}; const int mod = 1e9 + 7, gakki = 5 + 2 + 1 + 19880611 + 1e9; const int MAXN = 1e5 + 5, MAXM = 1e5 + 5, N = 1e4 + 5; const int MAXQ = 100010; int to[MAXM << 1], nxt[MAXM << 1], Head[MAXN], tot = 1; inline void addedge(int u, int v) { to[++tot] = v; nxt[tot] = Head[u]; Head[u] = tot; } inline void read(int &v) { v = 0; char c = 0; int p = 1; while (c < '0' || c > '9') { if (c == '-') { p = -1; } c = getchar(); } while (c >= '0' && c <= '9') { v = (v << 3) + (v << 1) + c - '0'; c = getchar(); } v *= p; } int n, m, l, r; bitset<N> B[10005], pre[10005], ans; int main() { ios_base::sync_with_stdio(0); cin.tie(0); int T; read(T); while (T--) { ans.reset(); read(n), read(m); for (int i = 1; i <= n; i++) { B[i].reset(); } B[0] = 1; for (int i = n; i >= 1; i--) { B[i] = 1; B[i] <<= i; for (int j = 2 * i; j <= n; j += i) { B[i] = B[i] ^ B[j]; } } pre[0] = 0; for (int i = 1; i <= n; i++) { pre[i] = pre[i - 1] ^ B[i]; } for (int i = 1; i <= m; i++) { read(l), read(r); ans = ans ^ pre[l - 1] ^ pre[r]; printf("%d\n", ans.count()); } } return 0; }
B(比赛通过)
水题
C(比赛通过)
把不同的字符串看作是点
用AC自动机建边 跑一遍即可
D
最后没有时间了 没做出来 其实蛮简单的
在没有确定根之前 满足一对pilot要求的点是这两个点的子树
在确定了一个节点为根之后 就可以分为两种情况
1.两个pilot不是一条链上的 则两个点的子树+1
2.两个pilot是一条链上的 则先全部节点+1 再把父亲到这个儿子的子树(不包括父亲节点)-1 再把儿子的子树+1
最后看值为m的点有多少个 即为答案+1 -1的这些操作用一个数组维护 dfs从父亲到儿子作前缀和即可
可以倍增(NlogN)做 也可以tarjan+dfs序(N)
倍增:
加读入挂的话 可以减到5500ms
/*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; typedef unsigned long long ull; const int dir[8][2] = {{0, 1}, {1, 0}, {0, -1}, { -1, 0}, {1, 1}, {1, -1}, { -1, -1}, { -1, 1}}; const int mod = 1e9 + 7, gakki = 5 + 2 + 1 + 19880611 + 1e9; const int MAXN = 1e5 + 5, MAXM = 1e5 + 5; const int maxl = 25; const int MAXQ = 100010; int to[MAXM << 1], nxt[MAXM << 1], Head[MAXN], tot = 1; inline void addedge(int u, int v) { to[++tot] = v; nxt[tot] = Head[u]; Head[u] = tot; } int flag = 0; int n, m, anser = 0; int presum[MAXN]; int grand[MAXN][maxl]; //x向上跳2^i次方的节点,x到他上面祖先2^i次方的距离 int gw[MAXN][maxl]; //维护距离的数组 int depth[MAXN];//深度 int root; int N; //N的意思是最多能跳几层 void dfs(int x)//dfs建图 { for (int i = 1; i <= N; i++) //第一个几点就全部都是0,第二个节点就有变化了,不理解的话建议复制代码输出下这些数组 { grand[x][i] = grand[grand[x][i - 1]][i - 1]; //倍增 2^i=2^(i-1)+2^(i-1) } for (int i = Head[x]; i; i = nxt[i]) { int v = to[i]; if (v != grand[x][0]) { depth[v] = depth[x] + 1; grand[v][0] = x; dfs(v); } } } void Init() { tot = 1; anser = 0; for (int i = 1; i <= n; i++) { presum[i] = Head[i] = 0; } N = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先 depth[root] = 0; //根结点的祖先不存在,用-1表示 depth[0] = -1; memset(grand, 0, sizeof(grand)); } int lca(int a, int b) { if (depth[a] > depth[b]) { swap(a, b); //保证a在b上面,便于计算 } int ans = 0; for (int i = N; i >= 0; i--) //类似于二进制拆分,从大到小尝试 { if (depth[a] < depth[b] && depth[grand[b][i]] >= depth[a]) //a在b下面且b向上跳后不会到a上面 { b = grand[b][i]; //先把深度较大的b往上跳 } } if (a == b) { return a; } for (int j = N; j >= 0; j--) //在同一高度了,他们一起向上跳,跳他们不相同节点,当全都跳完之后grand【a】【0】就是lca,上面有解释哈。 { if (grand[a][j] != grand[b][j]) { a = grand[a][j]; b = grand[b][j]; } } return grand[a][0]; } int lca2(int a, int b) { if (depth[a] > depth[b]) { swap(a, b); //保证a在b上面,便于计算 } int ans = 0; for (int i = N; i >= 0; i--) //类似于二进制拆分,从大到小尝试 { if (depth[a] + 1 < depth[b] && depth[grand[b][i]] >= depth[a] + 1) //a在b下面且b向上跳后不会到a上面 { b = grand[b][i]; //先把深度较大的b往上跳 } } return b; } void getadd(int x, int fa) { presum[x] += presum[fa]; for (int i = Head[x]; i; i = nxt[i]) { int v = to[i]; if (v != fa) { getadd(v, x); } } } int main() { ios_base::sync_with_stdio(0); cin.tie(0); root = 1; int T; scanf("%d",&T); while (T--) { int u, v; scanf("%d %d", &n, &m); Init(); for (int i = 1; i <= n - 1; i++) { scanf("%d %d", &u, &v); addedge(u, v), addedge(v, u); } dfs(root); for (int i = 1; i <= m; i++) { scanf("%d %d", &u, &v); int now = lca(u, v); //cout << "lca" << now << endl; if (now == u || now == v) { int cnt = lca2(u, v); //cout << "lca2 " << cnt << endl; if (now == u) { presum[1]++; presum[cnt]--; presum[v]++; } else { presum[1]++; presum[cnt]--; presum[u]++; } } else { presum[u]++, presum[v]++; } } getadd(1, 0); // for (int i = 1; i <= n; i++) // { // cout << "presum" << i << " " << presum[i] << endl; // } for (int i = 1; i <= n; i++) { if (presum[i] == m) { //cout << i << endl; anser++; } } cout<<anser<<endl; } return 0; }
tarjan做法:
没有用到dfs序 只是dfs的时候记录了一下当前节点的儿子节点QQQnxt[u]=v
极限可以做到2500ms
更新:莫名其妙他们的OJ变快(正常)了 跑了340ms 上面的倍增则跑了1000ms
/*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; typedef unsigned long long ull; const int dir[8][2] = {{0, 1}, {1, 0}, {0, -1}, { -1, 0}, {1, 1}, {1, -1}, { -1, -1}, { -1, 1}}; const int mod = 1e9 + 7, gakki = 5 + 2 + 1 + 19880611 + 1e9; const int MAXN = 1e5 + 5, MAXM = 1e5 + 5, N = 1e5 + 5; const int MAXQ = 100010; int to[MAXM << 1], nxt[MAXM << 1], Head[MAXN], tot = 1; inline void addedge(int u, int v) { to[++tot] = v; nxt[tot] = Head[u]; Head[u] = tot; } inline void read(int &v) { v = 0; char c = 0; int p = 1; while (c < '0' || c > '9') { if (c == '-') { p = -1; } c = getchar(); } while (c >= '0' && c <= '9') { v = (v << 3) + (v << 1) + c - '0'; c = getchar(); } v *= p; } int QQQnxt[MAXN]; int QQQanser[MAXN]; pair<int, int> QQQ[MAXM]; int n, m, anser = 0; int presum[MAXN], ans[MAXN]; bool vis[MAXN];//访问标记 int ancestor[MAXN];//祖先 struct Query { int q, next; int index;//查询编号 } query[MAXQ * 2]; int tt, Q, h[MAXQ], answer[MAXQ]; int F[MAXN];//需要初始化为-1 int find(int x) { if (F[x] == -1) { return x; } return F[x] = find(F[x]); } void bing(int u, int v) { int t1 = find(u); int t2 = find(v); if (t1 != t2) { F[t1] = t2; } } inline void add_query(int u, int v, int index) { query[tt].q = v; query[tt].next = h[u]; query[tt].index = index; h[u] = tt++; query[tt].q = u; query[tt].next = h[v]; query[tt].index = index; h[v] = tt++; } void LCA(int u) { ancestor[u] = u; vis[u] = true; for (int i = Head[u]; i; i = nxt[i]) { int v = to[i]; QQQnxt[u] = v; if (vis[v]) { continue; } LCA(v); bing(u, v); ancestor[find(u)] = u; } for (int i = h[u]; i != -1; i = query[i].next) { int v = query[i].q; if (vis[v]) { answer[query[i].index] = ancestor[find(v)]; if (answer[query[i].index] == v) { QQQanser[query[i].index] = QQQnxt[v]; } } } } void init() { tt = tot = 1; anser = 0; for (int i = 1; i <= n; i++) { ancestor[i] = presum[i] = Head[i] = 0; vis[i] = F[i] = h[i] = -1; } } void getadd(int x, int fa) { presum[x] += presum[fa]; for (int i = Head[x]; i; i = nxt[i]) { int v = to[i]; if (v != fa) { getadd(v, x); } } } int main() { ios_base::sync_with_stdio(0); cin.tie(0); int T; read(T); while (T--) { int u, v; read(n), read(m); init(); for (int i = 1; i <= n - 1; i++) { read(u), read(v); addedge(u, v), addedge(v, u); } for (int i = 0; i < m; i++) { read(u), read(v); add_query(u, v, i); QQQ[i] = make_pair(u, v); } LCA(1); for (int i = 0; i < m; i++) { //cout << answer[i] << endl; if (answer[i] == QQQ[i].first) { //cout<<QQQanser[i]<<endl; presum[1]++; presum[QQQanser[i]]--; presum[QQQ[i].second]++; } else if (answer[i] == QQQ[i].second) { //cout<<QQQanser[i]<<endl; presum[1]++; presum[QQQanser[i]]--; presum[QQQ[i].first]++; } else { presum[QQQ[i].first]++; presum[QQQ[i].second]++; } } getadd(1, 0); // for (int i = 1; i <= n; i++) // { // cout << "presum" << i << " " << presum[i] << endl; // cout << "ans" << i << " " << ans[i] << endl; // } for (int i = 1; i <= n; i++) { if (presum[i] == m) { //cout << i << endl; anser++; } } printf("%d\n", anser); } return 0; }
E(比赛通过)
注意年=月=日的特殊合法情况即可
F(比赛通过)
如果知道中位数的话 我们就可以n2logn暴力地知道答案 剩下就是怎么找中位数的问题
找中位数可以用权值线段树来做 注意单个点权值占一半以上的情况
总复杂度3*T*n2logn/2
G
无视
H(比赛通过)
水题
I(比赛通过)
树分治中点分治的一个小部分 变成带权的了 直接dfs一次即可
J(比赛通过)
结论题
很容易可以知道前三个我们肯定是可以确认是原数列的前三个
因为A0=0 A0+A1 A0+A2这三个肯定是最小的
比如样例0 1 2 2 我们可以先确认前三个0 1 2
则这三个产生的数列是1 2 3 接下来我们看与目标数列1 2 2 3 3 4对比最小的缺什么
很容易发现缺了个2 所以我们必须补个2
因为数列是非递减的Ai与之前数相加产生的数不大于Ai+1与之前数相加所产生的数
这样继续check直到数列被填满
写的话就是直接暴力找 看起来复杂度会爆炸 但其实中间很多就直接break相当于剪枝了 能过
K
题意:
你要玩一个猜数游戏 答案为X 你最多只能问N次 问的时间最多不能超过V
第i次询问你可以选一个数Y猜 如果Y大于X的话 会花费Ai的时间 不然的话会花费Ai+Bi的时间
你只有每次猜完后才可以猜下一次 问你最后可以从1开始猜到的最大区间长度为多少
解:
dp dp[i][j]表示只使用询问 i到n 而且时间不超过j所能知道的答案