2012 Multi-University #9
计算几何 A Farmer Greedy
题意:n个点选3个组成三角形,问m个点在三角形内的数字是奇数的这样的三角形个数.
分析:暴力O(N^3*M)竟然能过!我写的搓,加了优化才过掉.正解是先处理出每条线段正下方点的个数,然后枚举每个三角形O(1)计算,cnt[i][j] + cnt[j][k] - cnt[i][k](i,j,k已经对应的点按照x坐标排序),复杂度(n^2*m+n^3).
贴上别人的代码:
#include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> using namespace std; typedef long long ll; const int INF = 0x7fffffff; const int N = 1000 + 5; struct point{ ll x, y; }a[N], b[N]; int p[N][N]; bool cmp(point u, point v){ return u.x < v.x; } ll cross(point u, point v, point w){ return (v.x - u.x) * (w.y - u.y) - (v.y - u.y) * (w.x - u.x); } int main(){ int n, m; int cas = 0; while(scanf("%d%d", &n, &m) == 2){ memset(p, 0, sizeof(p)); int ans = 0; for(int i = 1; i <= n; i ++)scanf("%I64d%I64d", &a[i].x, &a[i].y); for(int i = 1; i <= m; i ++)scanf("%I64d%I64d", &b[i].x, &b[i].y); sort(a + 1, a + n + 1, cmp); for(int i = 1; i <= n; i ++){ for(int j = i + 1; j <= n; j ++){ for(int k = 1; k <= m; k ++){ if(cross(a[i], a[j], b[k]) < 0 && a[i].x < b[k].x && b[k].x < a[j].x)p[i][j] += 1; } } } for(int i = 1; i <= n; i ++) for(int j = 1; j < i; j ++)p[i][j] = p[j][i]; for(int i = 1; i <= n; i ++){ for(int j = i + 1; j <= n; j ++){ for(int k = j + 1; k <= n; k ++){ int tmp = p[i][k] - p[i][j] - p[j][k]; if(tmp < 0)tmp = -tmp; if(tmp % 2) ans += 1; } } } printf("Case %d: ", ++cas); printf("%d\n", ans); } return 0; }
01背包+贪心 B Grid
题意:两个人前后刷墙,问最多能刷多少墙.
分析: 先对位置排序,刷墙最好从最左边刷,那么dp[i]保存了刷到i墙时所需的最小刷墙数,枚举前后点取最小值.
#include <bits/stdc++.h> const int N = 1e3 + 5; const int INF = 0x3f3f3f3f; std::vector<std::pair<int, int> > A[2]; int dp[2][N]; int n, m; void solve(int &bv, int &bs) { memset (dp, INF, sizeof (dp)); dp[0][0] = dp[1][0] = 0; for (int k=0; k<2; ++k) { std::sort (A[k].begin (), A[k].end ()); for (int i=0; i<A[k].size (); ++i) { int pos = A[k][i].first, x = A[k][i].second; for (int j=pos; j>=x; --j) { dp[k][j] = std::min (dp[k][j], dp[k][j-x] + 1); } } } for (int i=0; i<=n; ++i) { for (int j=0; j<=n-i; ++j) { int tval = i + j; int tstep = dp[0][i] + dp[1][j]; if ((tval > bv && tstep <= m) || (tval == bv && tstep < bs)) { bv = tval; bs = tstep; } } } } int main() { int T; scanf ("%d", &T); for (int cas=1; cas<=T; ++cas) { scanf ("%d%d", &n, &m); A[0].clear (); A[1].clear (); for (int i=0; i<m; ++i) { int op, pos, x; scanf ("%d%d%d", &op, &pos, &x); if (op == 1) { A[0].push_back (std::make_pair (pos, x)); } else { A[1].push_back (std::make_pair (n + 1 - pos, x)); } } int bestv = 0, bests = 0; solve (bestv, bests); printf ("Case %d: %d %d\n", cas, bestv, bests); } return 0; }
树形DP D How to paint a tree
题意:两种操作,1.可以把某棵子树颜色全反转; 2.两个点之间的最短路径上的点颜色反转(可以是同一个点).问最少操作几次能使整棵树颜色相同.
分析:定义状态dp[u][i(0/1)][j(0/1)]表示u结点的子树颜色为i时,是否与父亲节点连接的最小操作数.其中第三维为1表示u与父亲节点连接将要做一次二操作,此时u子树的状态是除u以外的点都与u颜色相反,也就是u点进行二操作后才使得子树颜色相同.
其他类似不写了.
#include <bits/stdc++.h> const int N = 1e4 + 5; const int INF = 0x3f3f3f3f; int a[N]; std::vector<int> edge[N]; int dp[N][2][2]; int n; void _min(int &a, int b) { if (a > b) { a = b; } } void DFS(int u, int fa) { int col = a[u]; int sum[2] = {0}; int mn[2][2]; for (int i=0; i<2; ++i) { for (int j=0; j<2; ++j) { dp[u][i][j] = INF; mn[i][j] = INF; } } int son = 0; for (auto v: edge[u]) { if (v == fa) { continue; } son++; DFS (v, u); sum[0] += dp[v][0][0]; sum[1] += dp[v][1][0]; int sub = -dp[v][0][0] + dp[v][1][1]; if (sub <= mn[0][0]) { mn[0][1] = mn[0][0]; mn[0][0] = sub; } else if (sub < mn[0][1]) { mn[0][1] = sub; } sub = -dp[v][1][0] + dp[v][0][1]; if (sub <= mn[1][0]) { mn[1][1] = mn[1][0]; mn[1][0] = sub; } else if (sub < mn[1][1]) { mn[1][1] = sub; } } if (!son) { dp[u][col][0] = 0; dp[u][col^1][0] = 1; dp[u][col][1] = 0; dp[u][col^1][1] = 1; } else { _min (dp[u][col][0], sum[col]); _min (dp[u][col][1], sum[col^1]); _min (dp[u][col][1], sum[col^1]+mn[col^1][0]); _min (dp[u][col^1][0], sum[col^1]+mn[col^1][0]+mn[col^1][1]+1); int tmp[2][2]; for (int i=0; i<2; ++i) { for (int j=0; j<2; ++j) { tmp[i][j] = dp[u][i][j]; } } for (int i=0; i<2; ++i) { _min (dp[u][i][0], tmp[i^1][1] + 1); _min (dp[u][i][0], tmp[i][1] + 2); _min (dp[u][i][0], tmp[i^1][0] + 1); _min (dp[u][i][1], tmp[i^1][1] + 1); } } } int solve() { DFS (1, 0); int ret = std::min (dp[1][0][0], dp[1][1][0]); return ret; } int main() { int cas = 0; while (scanf ("%d", &n) == 1) { for (int i=1; i<=n; ++i) { edge[i].clear (); } for (int u, v, i=1; i<n; ++i) { scanf ("%d%d", &u, &v); edge[u].push_back (v); edge[v].push_back (u); } for (int i=1; i<=n; ++i) { scanf ("%d", a+i); } printf ("Case %d: %d\n", ++cas, solve ()); } return 0; }
状压DP F Moving Bricks
题意:给n个砖头坐标(n<20)和起点,每一次可以从起点出发,抱一块或者两块砖头原路返回,问所走的最短距离以及抱砖的顺序(字典序最小)
分析:明显的状态压缩DP,转移方程:
,
然后想了想直接for循环转移是对的(因为每次比上一个状态最多多了1个),当然BFS也对,不过慢了许多。最后在更新的时候记录从哪些砖头转移过来,从小到大输出(有两块砖顺便输出)。
#include <bits/stdc++.h> const int N = 1 << 20 + 5; const int INF = 0x3f3f3f3f; int x[21], y[21]; int dis[21][21]; int dp[N]; struct Info { int c, id1, id2, nex; }pre[N]; int lk[21]; bool vis[21]; int n, S; int calc_dis(int i, int j) { return (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]); } void DP(int s) { for (int i=0; i<S; ++i) { dp[i] = INF; } dp[s] = 0; //std::queue<int> que; que.push (s); //while (!que.empty ()) { for (int u=0; u<S; ++u) { //int u = que.front (); que.pop (); int v; for (int i=0; i<n; ++i) { if ((u >> i) & 1) { continue; } v = u | (1 << i); if (dp[v] > dp[u] + dis[n][i] * 2) { dp[v] = dp[u] + dis[n][i] * 2; pre[v] = (Info) {1, i, -1, u}; //que.push (v); } for (int j=i+1; j<n; ++j) { if ((u >> j) & 1) { continue; } v = u | (1 << i) | (1 << j); if (dp[v] > dp[u] + dis[n][i] + dis[i][j] + dis[j][n]) { dp[v] = dp[u] + dis[n][i] + dis[i][j] + dis[j][n]; pre[v] = (Info) {2, i, j, u}; //que.push (v); } } } } } int main() { int T; scanf ("%d", &T); for (int cas=1; cas<=T; ++cas) { int tx, ty; scanf ("%d%d", &tx, &ty); scanf ("%d", &n); for (int i=0; i<n; ++i) { scanf ("%d%d", &x[i], &y[i]); } x[n] = tx; y[n] = ty; for (int i=0; i<=n; ++i) { for (int j=i; j<=n; ++j) { dis[i][j] = dis[j][i] = calc_dis (i, j); } } S = 1 << n; int start = 0; DP (start); int end = S - 1; printf ("Case %d:\n%d\n", cas, dp[end]); memset (lk, -1, sizeof (lk)); while (end != start) { int a = pre[end].id1, b = pre[end].id2; if (b != -1) { if (a > b) { std::swap (a, b); } lk[a] = b; } end = pre[end].nex; } memset (vis, false, sizeof (vis)); for (int i=0; i<n; ++i) { if (vis[i]) { continue; } vis[i] = true; if (i == 0) { printf ("1"); } else { printf (" %d", i + 1); } if (lk[i] != -1 && !vis[lk[i]]) { vis[lk[i]] = true; printf (" %d", lk[i] + 1); } } puts (""); } return 0; }
数学 G Quadrilateral
题意:求任意四边形面积最大值
分析:可以三分对角线长度.也可以用公式,结论是当四边形的四个顶点都在圆上最大.详细解释
#include <bits/stdc++.h> int a[4]; double calc(double p) { return sqrt ((p-a[0]) * (p-a[1]) * (p-a[2]) * (p-a[3])); } int main() { int n; scanf ("%d", &n); for (int cas=1; cas<=n; ++cas) { double mx = 0, p = 0; for (int j=0; j<4; ++j) { scanf ("%d", &a[j]); if (mx < a[j]) { mx = a[j]; } p += a[j]; } p /= 2; printf ("Case %d: ", cas); std::sort (a, a+4); if (a[0] + a[1] + a[2] <= mx) { puts ("-1"); } else { printf ("%.6f\n", calc (p)); } } return 0; }
博弈 H Stone Game
题意:n长度,左右分别有k个石头,每次白往左,黑往右,移动到最近空的位置,不能移动胜.白先手,问谁必胜.
分析:
k=1时
很容易看出,n为奇数则后手获胜,n为偶数则先手获胜
k>1时
如果n=2*k+1,则棋盘中只有一个空白的格子,每次移动必须移动到当前的空白格子上。先手方可以先随意选择一颗棋子占据中间的位置,然后双方互有移动,移动过程中双方肯定都会选择一颗在己方半场的棋子移动到对方半场里。直到后手方还剩下一颗己方半场的棋子时,先手方把占据中间的棋子移动到对方半场,此时后手方必须移动剩下的这颗棋子到空出的中间的格子里,先手方再把最后一颗棋子移动到新空出的位置即可获胜。
如果n>2*k+1,那么棋盘中就有了多余一个的空白格子。如果n为奇数,先手方只要每次移动己方最靠后的一颗棋子即可获胜。并且第一次移动必须选择1号棋子,否则会让自己的棋子中间留下空白相当于把先手让给了对方,这时对方只要采用先手策略即可获胜;如果n为偶数,那么先手方只需采用n=2*k+1时的策略在双方交会的时候保住先手即可,当然此时的第一步还是1号棋子,否则将失去先手优势。
数位DP J X mod f(x)
题意:问在区间[l, r]范围内满足x % f(x) == 0 (f(x)为所有位数相加的和)的个数.
分析:做掉这题,终于搞明白这一类计数问题用数位DP做.开始我自己推是从低位开始,保存了当前的和以及它%(x*10^i+sum)的信息为下次递推用,但是发现状态信息很难保存,卒~.后来听说有模板,之前写过更恶心的AC自动机+数位DP,那时对记忆化深搜的巧妙方法影响深刻.dp[len][sum][%mod][mod]一维表示长度,一维表示数位和,一维表示对mod取模,该mod是全局,即对所有mod试一次.判断符合的条件就是%mod的值为0,且数位和为mod.
#include <bits/stdc++.h> //len,sum,%MOD,MOD int dp[11][82][81][82]; int digit[10]; int DFS(int len, int sum, int val, int mod, bool limit) { if (len == -1) { return val == 0 && sum == mod; } int &now = dp[len][sum][val][mod]; if (now != -1 && !limit) { return now; } int ret = 0; int d = limit ? digit[len] : 9; for (int i=0; i<=d; ++i) { ret += DFS (len - 1, sum + i, (val * 10 + i) % mod, mod, limit && i == d); } if (!limit) { now = ret; } return ret; } int calc(int x) { int p = 0; while (x) { digit[p++] = x % 10; x /= 10; } int ret = 0; for (int i=1; i<=81; ++i) { ret += DFS (p - 1, 0, 0, i, true); } return ret; } int main() { int T; scanf ("%d", &T); memset (dp, -1, sizeof (dp)); for (int cas=1; cas<=T; ++cas) { int l, r; scanf ("%d%d", &l, &r); printf ("Case %d: %d\n", cas, calc (r) - calc (l-1)); } return 0; }