[题解]NOIP2015提高组の题解集合 - by xyz32768
2017-10-29 16:22
351 查看
Day 1
T1 magic
模拟题。不断地往右上或往下走,注意边界。代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 55; int n, a ; int main() { //freopen("magic.in", "r", stdin); //freopen("magic.out", "w", stdout); int i, j, x = 1, y; n = read(); y = n + 1 >> 1; for (i = 1; i <= n * n; i++) { a[x][y] = i; if (i % n == 0) x++; else x--, y++; x = (x + n) % n; if (!x) x = n; y = (y + n) % n; if (!y) y = n; } for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++) printf("%d ", a[i][j]); printf("\n"); } return 0; }
T2 message
把每个i向Ti连边,求该有向图的最小环。有很多种做法。其中一种是,从一个点u开始遍历,不断地往Tu走,如果走到了这一次已经遍历过的节点,那么就找到了一个环。注意细节。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 2e5 + 5, INF = 0x3f3f3f3f; int n, t , ans = INF, vis , times; void go(int x) { int tmp = times; while (1) { int v = t[x]; if (vis[x] && vis[x] <= tmp) return; if (vis[v] > tmp) return (void) (ans = min(ans, times - vis[v] + 2)); vis[x] = ++times; x = v; } } int main() { //freopen("message.in", "r", stdin); //freopen("message.out", "w", stdout); int i; n = read(); for (i = 1; i <= n; i++) t[i] = read(); for (i = 1; i <= n; i++) if (!vis[i]) go(i); cout << ans << endl; return 0; }
T3 landlords
不考虑顺子,那么一次打多张一定是最优的。所以可以用贪心求解这一部分。对于顺子,就爆搜。代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 25, INF = 0x3f3f3f3f; int n, cnt , typ[] = {0, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, Ans, tmp ; int cc() { int i, CN = 0; memset(tmp, 0, sizeof(tmp)); for (i = 0; i <= 13; i++) tmp[cnt[i]]++; while (tmp[4] >= 1 && tmp[2] >= 2) tmp[4]--, tmp[2] -= 2, CN++; while (tmp[4] >= 1 && tmp[1] >= 2) tmp[4]--, tmp[1] -= 2, CN++; while (tmp[4] >= 1 && tmp[2] >= 1) tmp[4]--, tmp[2]--, CN++; while (tmp[3] >= 1 && tmp[2] >= 1) tmp[3]--, tmp[2]--, CN++; while (tmp[3] >= 1 && tmp[1] >= 1) tmp[3]--, tmp[1]--, CN++; return CN + tmp[1] + tmp[2] + tmp[3] + tmp[4]; } void dfs(int dep) { if (dep >= Ans) return; int i, j, k; Ans = min(Ans, dep + cc()); for (i = 2; i <= 13; i++) for (j = 1; j <= 3; j++) { if (cnt[i] < j) continue; int tt = 0; for (k = i; cnt[k] >= j; k++) tt += j, cnt[k] -= j; for (; (--k) >= i;) { if (tt >= 5) dfs(dep + 1); tt -= j, cnt[k] += j; } } } void work() { int i; memset(cnt, 0, sizeof(cnt)); for (i = 1; i <= n; i++) cnt[typ[read()]]++, read(); Ans = INF; dfs(0); printf("%d\n", Ans); } int main() { //freopen("landlords.in", "r", stdin); //freopen("landlords.out", "w", stdout); int T = read(); n = read(); while (T--) work(); return 0; }
Day 2
T1 stone
基础的二分题。二分最短跳跃距离后判定是否可能。代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while< 1b024 /span> ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 5e4 + 5; int L, n, m, a , minv; bool check(int mid) { int i, tot = 0, pre = 0; for (i = 1; i <= n + 1; i++) if (a[i] - pre < mid) tot++; else pre = a[i]; return tot <= m; } int solve() { int l = minv, r = 1e9, mid; while (l <= r) { mid = l + r >> 1; if (check(mid)) l = mid + 1; else r = mid - 1; } return r; } int main() { //freopen("stone.in", "r", stdin); //freopen("stone.out", "w", stdout); int i; L = read(); n = read(); m = read(); for (i = 1; i <= n; i++) a[i] = read(), minv = min(minv, a[i]); a[n + 1] = L; printf("%d\n", solve()); return 0; }
T2 substring
考虑DP。定义状态f[i][j][k][0/1]表示字符串A的前i个字符和字符串B的前j个字符用了k个子串,第四维为1表示A字符串的第i个字符必须被选出,为0表示A字符串的第i个字符不能被选出。
那么很容易得出转移:
1、对于任意一个0≤i≤n,f[i][0][0][0]=1。
2、f[i][j][k][0]=f[i−1][j][k][0]+f[i−1][j][k][1]
3、如果A[i]=B[j],那么f[i][j][k][1]=f[i−1][j−1][k][1]+f[i−1][j−1][k−1][0]+f[i−1][j−1][k−1][1]
最后结果即为f[n][m][K][0]+f[n][m][K][1]。
考虑到空间问题,滚动f的第一维以节省空间。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 1005, M = 205, PYZ = 1e9 + 7; int n, m, K, f[2][M][M][2]; char a , b[M]; int main() { //freopen("substring.in", "r", stdin); //freopen("substring.out", "w", stdout); int i, j, k; n = read(); m = read(); K = read(); scanf("%s", a + 1); scanf("%s", b + 1); f[0][0][0][0] = 1; for (i = 1; i <= n; i++) { int op = i & 1; f[op][0][0][0] = 1; for (j = 1; j <= min(i, m); j++) for (k = 1; k <= min(j, K); k++) { f[op][j][k][0] = f[op][j][k][1] = 0; f[op][j][k][0] = (f[op ^ 1][j][k][0] + f[op ^ 1][j][k][1]) % PYZ; if (a[i] == b[j]) { f[op][j][k][1] = (f[op ^ 1][j - 1][k][1] + f[op ^ 1][j - 1][k - 1][0]) % PYZ; (f[op][j][k][1] += f[op ^ 1][j - 1][k - 1][1]) %= PYZ; } } } printf("%d\n", (f[n & 1][m][K][0] + f[n & 1][m][K][1]) % PYZ); return 0; }
T3 transport
首先存下每个运输计划的起点st[i],终点ed[i],最近公共祖先lca[i]以及耗时len[i]。先二分最短时间time。
在判定中,先标记出所有len[i]>time的运输计划,虫洞必须位于这些运输计划的路径的公共边上。
这里,为每个点赋予一个权值,初始为0。
然后对于任意一个len[i]>time的运输计划,把st[i]到ed[i]的路径上的所有点(lca[i]除外)的点权,全部加上1,
这样容易知道,最后如果一个点的点权等于所有len[i]>time的运输计划的个数,那么连接这个点和这个点的父亲的边被改造成虫洞后会降低所有len[i]>time的运输计划的耗时,降低量为这条边的边权。所以,在符合条件的边中,选取一条边权最大的边,判断是否:耗时最长的计划消耗的时间−选取的边的边权≤time。
对于路径加1的操作,可以使用树剖实现,但是常数较大。但是可以发现这里的询问都是单点询问,而且都在路径修改之后。对于这一点,可以使用差分数组代替树剖,将复杂度去掉一个log。
建立差分数组b[u]=val[u]−∑val[v],其中val[u]为u的点权,v为u的子节点。
对于u到v的路径加x(不含lca)的操作为:
b[u]+=x,b[v]+=x,b[lca]−=2x。
而单点询问就是求子树和。最后DFS一遍就可以求出所有点的点权。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 3e5 + 5, LogN = 23; int n, m, a , ecnt, nxt[N << 1], adj , go[N << 1], fa [LogN], dep , T , val[N << 1], dis , len , st , ed , maxv, _lca , top , v_res, cnt_now; void add_edge(int u, int v, int w) { nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w; } void dfs(int u, int fu) { int i; dep[u] = dep[fu] + 1; for (i = 1; i <= 21; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int e = adj[u], v; e; e = nxt[e]) { if ((v = go[e]) == fu) continue; fa[v][0] = u; dis[v] = dis[u] + val[e]; top[v] = val[e]; dfs(v, u); } } int lca(int u, int v) { int i; if (dep[u] < dep[v]) swap(u, v); for (i = 21; i >= 0; i--) { if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return u; } for (i = 21; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; } int dist(int i, int u, int v) { return dis[u] + dis[v] - (dis[_lca[i] = lca(u, v)] << 1); } void change(int i, int x) { T[st[i]] += x; T[ed[i]] += x; T[_lca[i]] -= x << 1; } int dfs_ans(int u, int fu) { int res_d = T[u]; for (int e = adj[u], v; e; e = nxt[e]) { if ((v = go[e]) == fu) continue; res_d += dfs_ans(v, u); } if (res_d == cnt_now) v_res = max(v_res, top[u]); return res_d; } bool check(int mid) { int i, res = 0; for (i = 0; i <= n; i++) T[i] = 0; cnt_now = 0; for (i = 1; i <= m; i++) if (len[i] > mid) cnt_now++, change(i, 1); if (!cnt_now) return 1; v_res = 0; dfs_ans(1, 0); return maxv - v_res <= mid; } int solve() { int l = 0, r = maxv, mid; while (l <= r) { mid = l + r >> 1; if (check(mid)) r = mid - 1; else l = mid + 1; } return l; } int main() { //freopen("transport.in", "r", stdin); //freopen("transport.out", "w", stdout); int i, x, y, z; n = read(); m = read(); for (i = 1; i < n; i++) { x = read(); y = read(); z = read(); add_edge(x, y, z); add_edge(y, x, z); } dfs(1, 0); for (i = 1; i <= m; i++) { st[i] = read(); ed[i] = read(); len[i] = dist(i, st[i], ed[i]); maxv = max(maxv, len[i]); } printf("%d\n", solve()); return 0; }
相关文章推荐
- [题解]NOIP2014提高组の题解集合 - by xyz32768
- {题解}[jzoj4326]NOIP2015提高组Day2 跳石头
- [NOIP2015提高&洛谷P2678]跳石头 题解(二分答案)
- {题解}[jzoj4328]NOIP2015提高组Day2 运输计划
- [题解]NOIP2016提高组の题解集合 - by xyz32768
- NOIP 2015提高组 题解+分析
- NOIP 2015 提高组 初赛
- 4325. 【NOIP2015提高组Day1】斗地主
- NOIp2015提高组 解题报告
- NOIP2015提高组复赛 解题报告
- C++——NOIP2015提高组day2 t1——跳石头
- POJ - 3258/USACO - Dec06 Silver/NOIP 2015 - 提高组 River Hopscotch 贪心+二分搜索
- 2015 Noip提高组 Day2
- NOIP2015提高组解析
- [NOIP2013提高&洛谷P1966]火柴排队 题解(树状数组求逆序对)
- NOIP 2015 提高组 复赛 day2 stone 跳石头
- NOIP2016 提高组 题解
- {题解}[jzoj4823] 【NOIP2016提高A组集训第1场10.29】小W学物理
- 过河[NOIP2015提高组][Codevs 1105]
- Vijos1981[Noip2015提高组]跳石头