29-30 考试总结

来源:COCI2011~2012 test1-test2部分题目。

T1 ples#

题目大意#

N个男生和N个女生参加舞会,跳舞时只能是一个男生一个女生,我们知道每个人的身高,同时某个男生只想和比他高(或者矮)的女生跳(有人跟你跳还挑三拣四的?),女生也是一样的,身高一样的人都不想与对方跳。给出所有数据,输出满足人们希望的前提下组成的最多对数。

分析#

开始拿到题看了看,哦?二分图最大匹配板子?

直到看到了浩瀚无边的数据范围。。。正当心中默念"哦豁"时,看到了很小的值域范围。。。

人生大起大落真刺激

恩,带权二分图最大匹配,因为值域很小,我们可以把每个值看做点,人数看做点权,按题目要求连边,男生女生分别连向源点和汇点,然后就是跑最大流了。

因为内存限制,所以要精确算开多少条边。

代码#

Copy
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> const int BASE = 1500; const int INF = 0x3f3f3f3f; inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } inline void hand_in() { freopen("ples.in", "r", stdin); freopen("ples.out", "w", stdout); } int n, s, t, maxflow; struct Node{ int l, r; }A[1005], B[1005]; inline int min(int a, int b) { return a < b ? a : b; } struct Sakura { int to, nxt, w; }sak[2200005]; int head[4010], cnt = 1; inline void add(int x, int y, int w) { ++cnt; sak[cnt].to = y, sak[cnt].w = w, sak[cnt].nxt = head[x], head[x] = cnt; ++cnt; sak[cnt].to = x, sak[cnt].w = 0, sak[cnt].nxt = head[y], head[y] = cnt; // printf("%d -> %d : %d\n", x, y, w); } int dep[4010]; inline bool bfs() { std :: queue<int> q; memset(dep, 0, sizeof dep); dep[s] = 1, q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u];i;i =sak[i].nxt) { int v = sak[i].to, w = sak[i].w; if (!dep[v] && w) { dep[v] = dep[u] + 1; if (v == t) return 1; q.push(v); } } } return 0; } inline int dfs(int u, int flow) { if (u == t) return flow; int rest = flow, rlow; for (int i = head[u];i && rest;i = sak[i].nxt) { int v = sak[i].to, w = sak[i].w; if (w && dep[v] == dep[u] + 1) { rlow = dfs(v, min(rest, w)); if (!rlow) dep[v] = 0; sak[i].w -= rlow; sak[i ^ 1].w += rlow; rest -= rlow; } } return flow - rest; } inline int Dinic() { int lowflow; while (bfs()) { while(lowflow = dfs(s, INF)) maxflow += lowflow; } return maxflow; } int main(){ hand_in(); n = read(); s = 4004, t = 4005; for (int i = 1, x;i <= n; ++i) { x = read(); if (x < 0) A[- x - BASE].l ++; else A[x - BASE].r ++; } for (int i = 1, x;i <= n; ++i) { x = read(); if (x < 0) B[- x - BASE].l ++; else B[x - BASE].r ++; } /* A.l 0~1000 A.r 1001~2001 B.l 2002~3002 B.r 3003~4003 */ /* S --- A.l / A.r 连源点 */ for (int i = 0;i <= 1000; ++i) { if (A[i].l) add(s, i, A[i].l); if (A[i].r) add(s, i + 1001, A[i].r); } /* A.l --- B.r 男高女矮 */ for (int i = 1;i <= 1000; ++i) { for (int j = 0;j < i; ++j) { if (A[i].l && B[j].r) add(i, j + 3003, INF); } } /* A.r --- B.l 男矮女高 */ for (int i = 0;i < 1000; ++i) { for (int j = i + 1;j <= 1000; ++j) { if (A[i].r && B[j].l) add(i + 1001, j + 2002, INF); } } /* B.l / B.r --- T 连汇点 */ for (int j = 0;j <= 1000; ++j) { if (B[j].l) add(j + 2002, t, B[j].l); if (B[j].r) add(j + 3003, t, B[j].r); } printf("%d", Dinic()); return 0; }

排序+贪心是正解???

好吧,的确是的。

我们可以发现,排序后让正数与负数匹配,用指针一直向后移动,那么会让后面的情况达到最优。

T2 sort#

题目大意#

对一个序列使用reverse函数排成升序,每次reverse序列中所有连续单调下降子序列,保证在第一次排序后,所有的连续单调下降子序列的长度都是偶数。求reverse函数的使用次数。

分析#

30pt做法

直接用reverse函数模拟,你甚至可以自己手写

100pt做法

首先,有个结论,在第一次对序列执行这种方法的排序后,序列中所有的连续单调下降子序列的长度要么是3要么是21排除在外)。

证明:考虑相邻的两个连续单调下降子序列,有两种情况:它们中间没有间隔;它们中间有一个元素的间隔。对于第一种情况,reverse这两个子序列后,只会在它们俩的交界处形成一个长度为2的单调下降序列;对于第二种情况,同理,会形成一个长度为3的单调下降序列。

又因为题目保证都为偶数,所以在第一次排序后,所有的连续单调下降子序列的长度都是2

这有什么性质?在之后我们每次reverse,就会让一个数的前面比它大的数减1,又因为递增序列所有数都比它前面的数大,所以联想到求逆序对。

所以这道题的解法就是先模拟排序一遍该序列,累加答案,然后计算当前序列的逆序对数,累加进答案里面。

代码#

Copy
#include<cstdio> #include<cstdlib> #include<algorithm> #define ll long long const int N = 100000 + 5; using std :: reverse; inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } inline void hand_in() { freopen("sort.in", "r", stdin); freopen("sort.out", "w", stdout); } int n, a[N]; ll ans; inline bool judge() { for (int i = 2;i <= n; ++i) { if (a[i] < a[i - 1]) return 1; } return 0; } int c[N]; inline int lowbit(int x) { return x & (-x); } inline void add(int x) { for (int i = x;i <= n; i += lowbit(i)) c[i] ++; } inline int ask(int x) { int res = 0; if (!x) return 0; for (int i = x;i;i -= lowbit(i)) res += c[i]; return res; } inline void solve() { int st = 1, ed = 1, len = 1; for (int i = 2;i <= n; ++i) { if (a[i] < a[i - 1]) { ed = i, len ++; } else { if (len > 1) { reverse(a + st, a + ed + 1); ans ++; } st = i, ed = i, len = 1; } } if (len > 1) { reverse(a + st, a + ed + 1); ans ++; } for (int i = n;i >= 1; --i) { ans += ask(a[i] - 1); add(a[i]); } } int main(){ hand_in(); n = read(); for (int i = 1;i <= n; ++i) a[i] = read(); solve(); printf("%lld", ans); return 0; }

T3 skakac(To be continue)#

题目大意#

略略略~

分析#

20pt做法

也许直接dfs

30~50pt做法

f[i][j][k]表示当前时间为i,棋盘位置为(j,k)是否到达的状态,然后就可以枚举i1的状态转移过来,用个滚动数组优化下,空间复杂度O(n2),时间复杂度O(Tn2)

同时,也可以上BFS

60~100pt做法

鬼知道数据有多恐怖,貌似正确的复杂度只有70pt???

考虑压缩每一行的状态,然后就可以O(Tn)转移。

难就难在处理图上的Ki,j

为了保证时间复杂度和空间复杂度,对Ki,j分开处理。

如果Ki,j>1000那么把在T以内的Ki,j的倍数记录下来,当T达到这个值的时候再处理。 如果Ki,j1000,可以把Ki,j分解质因数,设=Ki,j=p1c1p2c2...pkck,那么当T能同时整除1c1p2c2...pkck 时当前(i,j)就可到达。
g[i][j][k]表示第k行能整除pij时的情况,那么把T也分解质因数,棋盘的情况就可以表示为g[1][c1]&g[2][c2]...g[k][ck]
当然次数为0的也要考虑进去,为了节省时间可以令h[i][j]=g[i][0]&g[i+1][0]...g[j][0]

然后就可以预处理出所有状态下图对应的状态,也许写得有点问题,数据点过不全。

代码#

Copy
#include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long #define Re register const int P = 1000000007; const int BASE = 1e6; const int N = 30 + 5; using std :: vector; using std :: sort; inline void hand_in() { freopen("skakac.in", "r", stdin); freopen("skakac.out", "w", stdout); } inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } int n, t, st_x, st_y; int mp[N][N]; struct Node { int t, x, y; Node (int a = 0, int b = 0, int c = 0) : t(a), x(b), y(c) {} friend bool operator < (Node a, Node b) { return a.t < b.t; } }ans[N * N], rate[BASE + 5]; int oo, pp; /* 预处理质数 */ int prim[BASE + 5], vis[BASE + 5], tot, ps; inline void init() { for (Re int i = 2;i <= BASE; ++i) { if (!vis[i]) { prim[++tot] = i, vis[i] = tot; if (i <= 1000) ps = tot; /* 小优化:记录下1000以内的质数到了哪里 */ } for (Re int j = 1;j <= tot; ++j) { if ((ll)i * (ll)prim[j] > BASE) break; vis[i * prim[j]] = j; if (i % prim[j] == 0) break; } } } /* 对(x,y)上的k值进行质因数分解 */ /* h: h[i][j][k]表示 g: g[i][j][k]表示第k行能整除pi^j时的情况 */ int h[250][250][35], g[250][25][35]; inline void divide(int k, int x, int y) { for (Re int i = 1;i <= ps; ++i) { int ret = 0; while (k % prim[i] == 0) ret ++, k /= prim[i]; /* 既然x^ret能够到达,所以高于ret的次幂都行,最多达到2^20 */ for (;ret <= 20; ++ret) g[i][ret][x] |= (1 << y); } } /* 质因数分解 */ struct Divide { int pr, nm; friend bool operator < (Divide a, Divide b) { return a.pr < b.pr; } }dv[BASE + 5]; int w[35]; inline void work(int x) { int ct = 0, pos = 1; /* 会超时??? */ // for (int i = 1, ret;prim[i] <= x; ++i) { // if (x % prim[i]) continue; // ret = 0; // while (x % prim[i] == 0) ret ++, x /= prim[i]; // if (ret) dv[++ct].pr = i, dv[ct].nm = ret; // } while (x ^ 1) { int ret = 0, op = vis[x]; while (x % prim[op] == 0) ret ++, x /= prim[op]; dv[++ct].pr = op, dv[ct].nm = ret; } sort(dv + 1, dv + 1 + ct); for (int i = 1;i <= ct && dv[i].pr <= ps; ++i) { for (int j = 1;j <= n; ++j) { w[j] &= g[dv[i].pr][dv[i].nm][j]; } if (pos < dv[i].pr) { for (int j = 1;j <= n; ++j) { w[j] &= h[pos][dv[i].pr - 1][j]; } } pos = dv[i].pr + 1; } if (pos < ps) for (int i = 1;i <= n; ++i) w[i] &= h[pos][ps][i]; } int f[2][35], now, pre; int main(){ hand_in(); /* 读入 */ n = read(), t = read(), st_x = read(), st_y = read(); /* 预处理出质数 */ init(); /* 对地图分类处理:1000以上用倍数,反之分解它 */ for (Re int i = 1;i <= n; ++i) { for (Re int j = 1;j <= n; ++j) { mp[i][j] = read(); if (mp[i][j] >= 1500) { for (Re int k = 1;k * mp[i][j] <= t; ++k) rate[++pp] = Node(k * mp[i][j], i, j); } else { divide(mp[i][j], i, j); } } } /* 排序后可以依次考虑 */ sort(rate + 1, rate + 1 + pp); for (Re int i = 1;i <= ps; ++i) { for (Re int j = i;j <= ps; ++j) { for (Re int s = 1;s <= n; ++s) { /* 初始化全为1 */ h[i][j][s] = (1 << (n + 1)) - 1; for (Re int k = i;k <= j; ++k) { h[i][j][s] &= g[k][0][s]; } } } } f[0][st_x] |= (1 << st_y); for (Re int i = 1, p = 1;i <= t; ++i) { now ^= 1, pre = now ^ 1; for (Re int j = 1;j <= n; ++j) f[now][j] = 0, w[j] = (1 << (n + 1)) - 1; for (Re int j = 1;j <= n; ++j) { if (j > 1) f[now][j] |= (f[pre][j - 1] >> 2) | (f[pre][j - 1] << 2); if (j < n) f[now][j] |= (f[pre][j + 1] >> 2) | (f[pre][j + 1] << 2); if (j > 2) f[now][j] |= (f[pre][j - 2] >> 1) | (f[pre][j - 2] << 1); if (j < n - 1) f[now][j] |= (f[pre][j + 2] >> 1) | (f[pre][j + 2] << 1); } work(i); while (p <= pp && rate[p].t == i) w[rate[p].x] |= (1 << rate[p].y), ++p; for (Re int j = 1;j <= n; ++j) f[now][j] &= w[j]; } for (Re int i = 1;i <= n; ++i) { for (Re int j = 1;j <= n; ++j) { if (f[now][i] & (1 << j)) ans[++oo].x = i, ans[oo].y = j; } } printf("%d\n", oo); for (Re int i = 1;i <= oo; ++i) { printf("%d %d\n", ans[i].x, ans[i].y); } return 0; }

T4 kom#

题目大意#

给出N个互不相同的正整数,统计共有多少对数,它们有公共的一个数字(不一定在同一位置上)。

分析#

20pt做法

无脑暴力。

60pt做法

假的容斥。

考试时最先想到这个方法。

对每个数用二进制状态存下来O(n)扫描,然后对1024种状态容斥一下,就是O(n×1024)

貌似这种方法用bitset优化下就能过?

100pt做法

我们不需要知道所有数,只需要知道某个数出现了哪些数字,思考压缩状态,即只用出现了哪些数字来表示一个数,又因为数字只有0~9,所以整个状态总量就是210=1024。然后我们就可以枚举压缩后的状态啦,记得不能算自身,所以要减去自己与自己成对的情况。

代码#

Copy
#include<cstdio> #include<cstdlib> #include<cstring> #define ll long long inline void hand_in() { freopen("kom.in", "r", stdin); freopen("kom.out", "w", stdout); } int n, len, v, pan[1025]; char ch[20]; ll ret; int main(){ hand_in(); scanf("%d", &n), v = (1 << 10); for (int i = 1, a;i <= n; ++i) { scanf("%s", ch + 1); len = strlen(ch + 1), a = 0; for (int j = 1;j <= len; ++j) { a |= (1 << (ch[j] - '0')); } pan[a] ++; } for (int i = 0;i < v; ++i) { for (int j = 0;j < v; ++j) { if (i & j) { ret += (ll)pan[i] * (ll)pan[j]; if (i == j) ret -= (ll)pan[i]; } } } printf("%lld\n", ret >> 1); return 0; }

T5 fun#

题目大意#

略略略~

分析#

30pt做法

直接按照那个函数用dfs模拟即可。

100pt做法

我们可以发现,若一层循环中上下界为常数项,我们可以把它换在任何位置,对答案没有任何影响,只需要在统计答案时乘上这层循环的循环次数即可。

推广一下,当我们确定一个字母变量的值的时候,那么所有依赖于它的变量循环都可变为常数循环,我们任意交换是没有问题的。根据依赖关系,我们会发现是由一棵棵树组成的森林,每棵树相互之间是不会受到影响的,所以直接把每棵树相乘即为最终答案。

对于每棵树,我们从上往下遍历,就能依次确定各个变量的取值范围,然后将子树的值相乘,再上传就是答案。

用记忆化优化下复杂度。

代码#

Copy
#include<cstdio> #include<cstdlib> #include<cstring> #define ll long long const int P = 1000000007; const int N = 30; inline void hand_in() { freopen("fun.in", "r", stdin); freopen("fun.out", "w", stdout); } inline bool is_num(char *s) { if (s[1] < '0' || s[1] > '9') return 0; return 1; } int cnt, to[N << 1], nxt[N << 1], head[N]; inline void add(int x, int y) { ++cnt; to[cnt] = y, nxt[cnt] = head[x], head[x] = cnt; } inline int change_num(char *s) { int l = strlen(s + 1); int res = 0; for (int i = 1;i <= l; ++i) { res = res * 10 + s[i] - '0'; } return res; } ll dfs(int, int); ll f[N][100005], ans = 1; int n, l[N], r[N], rate[N]; char ls[10], rs[10]; inline ll find(int now, int lim) { ll res = 1; int p = head[now], nt; while (p) { nt = to[p]; res *= dfs(nt, lim) % P; p = nxt[p]; } return res; } inline ll dfs(int now, int lim) { if (~f[now][lim]) return f[now][lim]; int last = lim; if (rate[now] == 0) { while (last <= r[now] && f[now][last] == -1) last ++; if (last > r[now]) f[now][last] = 0; last --; while (last >= lim) { f[now][last] = (f[now][last + 1] + find(now, last)) % P; last --; } } else if (rate[now] == 1) { while (last >= l[now] && f[now][last] == -1) last --; if (last < l[now]) f[now][last] = 0; last ++; while (last <= lim) { f[now][last] = (f[now][last - 1] + find(now, last)) % P; last ++; } } else { f[now][lim] = 0; for (int i = l[now];i <= r[now]; ++i) { f[now][i] = find(now, i); f[now][lim] = (f[now][i] + f[now][lim]) % P; } } return f[now][lim]; } int main(){ // hand_in(); scanf("%d", &n); memset(f, -1, sizeof f); for (int i = 0;i < n; ++i) { scanf("%s %s", ls + 1, rs + 1); rate[i] = -1; if (is_num(ls)) { l[i] = change_num(ls); } else { rate[i] = 0; l[i] = ls[1] - 'a'; add(l[i], i); } if (is_num(rs)) { r[i] = change_num(rs); } else { rate[i] = 1; r[i] = rs[1] - 'a'; add(r[i], i); } } for (int i = 0;i < n; ++i) { if (rate[i] == -1) { ans = ans * dfs(i, 0) % P; } } printf("%lld", ans); return 0; }

T6 ras#

题目大意#

略略略~

分析#

30pt做法

把计算答案的式子列出来后会发现可以贪心。

即把序列按t的升序排序,然后直接计算答案。

对于每次修改,暴力修改,再重新排遍序,再计算答案。

100pt做法

思路大体没错,算法的瓶颈在于修改,也就是动态维护前缀和。

考虑使用数据结构权值线段树或权值树状数组。

可以把维护前缀和拆成两个操作,清除某位上的一个数,把一个数插入到某位上。

在操作的同时对ans进行维护。

注意需要开ll

代码#

Copy
#include<cstdio> #include<cstdlib> #include<algorithm> #define ll long long using std :: sort; const int N = 100000 + 5; inline int read(){ int f = 1, x = 0; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9'); do {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); return f * x; } inline void hand_in() { freopen("ras.in", "r", stdin); freopen("ras.out", "w", stdout); } int n, c; ll ans, s; struct Data { int l, t; }mk[200005], rsd[200005]; inline bool cmp(const Data &a, const Data &b) { return a.t < b.t; } struct Segment_Tree { struct Node { int l, r; ll x, t; }tr[N << 2]; #define ls (p << 1) #define rs ((p << 1) | 1) inline void build(int p, int l, int r) { tr[p].l = l, tr[p].r = r; if (l == r) return; int mid = (l + r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); } inline void change(int p, int x, int a, int b) { tr[p].x += a, tr[p].t += b; if (tr[p].l == tr[p].r) return; int mid = (tr[p].l + tr[p].r) >> 1; if (x <= mid) change(ls, x, a, b); else change(rs, x, a, b); } inline ll ask_x(int p, int l, int r) { if (l <= tr[p].l && tr[p].r <= r) { return tr[p].x; } int mid = (tr[p].l + tr[p].r) >> 1; ll res = 0; if (l <= mid) res += ask_x(ls, l, r); if (r > mid) res += ask_x(rs, l, r); return res; } inline ll ask_t(int p, int l, int r) { if (l <= tr[p].l && tr[p].r <= r) { return tr[p].t; } int mid = (tr[p].l + tr[p].r) >> 1; ll res = 0; if (l <= mid) res += ask_t(ls, l, r); if (r > mid) res += ask_t(rs, l, r); return res; } }st; int main(){ hand_in(); n = read(), c = read(); for (int i = 1;i <= n; ++i) { mk[i].l = read(), mk[i].t = read(); rsd[i] = mk[i], ans += mk[i].l; } st.build(1, 1, 100001); sort(mk + 1, mk + 1 + n, cmp); for (int i = 1;i <= n; ++i) { s += mk[i].t; ans -= s; st.change(1, mk[i].t, mk[i].t, 1); } printf("%lld\n", ans); for (int i = 1, id, l, t;i <= c; ++i) { id = read(), l = read(), t = read(); ans -= rsd[id].l - st.ask_x(1, 1, rsd[id].t) - st.ask_t(1, rsd[id].t + 1, 100001) * rsd[id].t; st.change(1, rsd[id].t, -rsd[id].t, -1); rsd[id].l = l, rsd[id].t = t; st.change(1, rsd[id].t, rsd[id].t, 1); ans += rsd[id].l - st.ask_x(1, 1, rsd[id].t) - st.ask_t(1, rsd[id].t + 1, 100001) * rsd[id].t; printf("%lld\n", ans); } return 0; }
posted @   SilentEAG  阅读(192)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
CONTENTS