http://acm.hdu.edu.cn/contests/contest_show.php?cid=882
1007 - Go Running
题意:每个人可以选择任意的时间开始,任意时间结束,从任意位置出发,向左或右跑步,给出了 n 个报告,每次告诉你在第 t [ i ] t[i] t [ i ] 秒时坐标 x [ i ] x[i] x [ i ] 处至少有一个人。问最少有几个人跑过步了。
最小点覆盖/二分图匹配
如果 x [ i ] − t [ i ] = x [ j ] − t [ j ] x[i]-t[i]=x[j]-t[j] x [ i ] − t [ i ] = x [ j ] − t [ j ] 则 i,j 可以视为同一个人向右跑。
如果 x [ i ] + t [ i ] = x [ j ] + t [ j ] x[i]+t[i]=x[j]+t[j] x [ i ] + t [ i ] = x [ j ] + t [ j ] 则 i,j 可以视为同一个人向左跑。
所以问题变为平面上 n 个点 ( x i , t i ) (x_i,t_i) ( x i , t i ) ,要用最少的斜率为正负 1 的直线穿过所有点。
本来也想过网络流,但是想着网络流复杂度太大了,但是其实 Dinic 在二分图上的复杂度是 O ( m n ) O(m\sqrt n) O ( m n ) 的。
每个点可以被两条直线选中,所以考虑把每个点变为 新图上两个点集的连边 x [ i ] − t [ i ] x[i]-t[i] x [ i ] − t [ i ] 连向 x [ i ] + t [ i ] x[i]+t[i] x [ i ] + t [ i ] 。
这样每点可以被两种选择选中,所以拆成两个点集,把点变成边,再求最小点覆盖的模型在紫书和挑战程序设计上都有,也算是比较常用了,需要记住。
再求最小点覆盖,这样就能保证新图上所有边都被选中,也就是原图上所有点都被选中了。
二分图的最小点覆盖等于最大匹配,所以就是求最大匹配。
Dinic 求二分图最大匹配的复杂度是 O ( m n ) O(m\sqrt n) O ( m n ) ,在这里是完全够用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <bits/stdc++.h> using namespace std;#define ll long long const int INF = 0x3f3f3f3f ;const ll inf = 0x3f3f3f3f3f3f3f3f ;const int N = 2e5 + 10 ;const ll mod = 1e9 + 7 ;const double eps = 1e-5 ;struct mxfl { int n = 0 ; int m = 0 ; int s, t; struct Edge { int from, to, cap, flow; }; vector<Edge>edges; vector<int >G[N]; void init (int _n, int _s, int _t ) { n = _n; s = _s; t = _t ; edges.clear (); for (int i = 0 ; i < n; i++)G[i].clear (); } void add_edge (int from, int to, int cap) { edges.push_back (Edge{ from,to,cap,0 }); edges.push_back (Edge{ to,from,0 ,0 }); m = (int )edges.size (); G[from].push_back (m - 2 ); G[to].push_back (m - 1 ); } bool vis[N]; int d[N], cur[N]; bool bfs () { memset (vis, 0 , sizeof (vis)); queue<int >Q; Q.push (s); d[s] = 0 ; vis[s] = 1 ; while (!Q.empty ()) { int x = Q.front (); Q.pop (); for (int i = 0 ; i < (int )G[x].size (); i++) { Edge& e = edges[G[x][i]]; if (!vis[e.to] && e.cap > e.flow) { vis[e.to] = 1 ; d[e.to] = d[x] + 1 ; Q.push (e.to); } } } return vis[t]; } int dfs (int x, int a) { if (x == t || a == 0 )return a; int flow = 0 , f; for (int & i = cur[x]; i < (int )G[x].size (); i++) { Edge& e = edges[G[x][i]]; if (d[x] + 1 == d[e.to] && (f = dfs (e.to, min (a, e.cap - e.flow))) > 0 ) { e.flow += f; edges[G[x][i] ^ 1 ].flow -= f; flow += f; a -= f; if (a == 0 )break ; } } return flow; } int maxflow () { int flow = 0 ; while (bfs ()) { memset (cur, 0 , sizeof (cur)); flow += dfs (s, INF); } return flow; } }MF; int T;int n;int t[N], x[N];vector<int >vc[2 ]; int main () { scanf ("%d" , &T); while (T--) { scanf ("%d" , &n); vc[0 ].clear (); vc[1 ].clear (); for (int i = 1 ; i <= n; i++) { scanf ("%d%d" , &t[i], &x[i]); vc[0 ].push_back (x[i] - t[i]); vc[1 ].push_back (x[i] + t[i]); } sort (vc[0 ].begin (), vc[0 ].end ()); sort (vc[1 ].begin (), vc[1 ].end ()); vc[0 ].erase (unique (vc[0 ].begin (), vc[0 ].end ()), vc[0 ].end ()); vc[1 ].erase (unique (vc[1 ].begin (), vc[1 ].end ()), vc[1 ].end ()); int S = 0 , T = (int )vc[0 ].size () + (int )vc[1 ].size () + 1 ; MF.init (T + 1 , S, T); for (int i = 1 ; i <= (int )vc[0 ].size (); i++)MF.add_edge (S, i, 1 ); for (int i = 1 ; i <= (int )vc[1 ].size (); i++)MF.add_edge ((int )vc[0 ].size () + i, T, 1 ); for (int i = 1 ; i <= n; i++) { int a = lower_bound (vc[0 ].begin (), vc[0 ].end (), x[i] - t[i]) - vc[0 ].begin () + 1 ; int b = lower_bound (vc[1 ].begin (), vc[1 ].end (), x[i] + t[i]) - vc[1 ].begin () + 1 + (int )vc[0 ].size (); MF.add_edge (a, b, 1 ); } printf ("%d\n" , MF.maxflow ()); } return 0 ; }
1012 - Last Problem
题意:在一个二维坐标系的整点上写数字,要写一个数字 t 必须满足写下的这个数字四周已经写好了 t-1,t-2,t-3,t-4,非正数可以不管。要求写出数字 n。给出操作步骤。
构造
玩过2048的应该都清楚越大的数字要构造出需要的操作数是远大于小数的,所以肯定是习惯性地利用之前已经构造出的大数来构造出更大的,而不是从头开始。
所以也就可以尝试出上图中间一列,最终的 n 也就会出现在这一列。
接着就是各种尝试,目的就是要找出一种 “对称” 的构造方式。
如上图,要写下一个数字 t,需要按照 上,左,右,下 的顺序写下 t-1,t-2,t-3,t-4。
要从一开始就找到这个规律肯定是很难的,但是最开始可能会发现每一列都是 1,2,3 这样递增,那么就不断向着这个方向尝试,直到能找出可以递归的规律。
数据很小,其实只要能构造出来,必定不会超过限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> using namespace std;#define ll long long const int INF = 0x3f3f3f3f ;const ll inf = 0x3f3f3f3f3f3f3f3f ;const int N = 2e6 + 10 ;const ll mod = 1e9 + 7 ;int n;int a[3000 ][3000 ];void dfs (int x, int y, int num) { if (a[x][y] == num || num <= 0 )return ; dfs (x, y + 1 , num - 1 ); dfs (x - 1 , y, num - 2 ); dfs (x + 1 , y, num - 3 ); dfs (x, y - 1 , num - 4 ); printf ("%d %d %d\n" , x, y, num); a[x][y] = num; } int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i++) { dfs (1000 , 1000 - i + 1 , i); } return 0 ; }
1003 - Contest of Rope Pulling
题意:两个班级分别有 n,m 个人,每人有 w,v 两种属性,要求每班各选一些人,且两班的 w 的和相同,v 的和最大。
随机+dp
把第二个班级的人 w 取负,就相当于在 n+m 个人中选一个子集,w 和为 0,v 和最大。
这是很典型的01背包。复杂度应该是 值域*(n+m),复杂度太大。
但是其实并不需要取整个值域,因为最终我们需要的是 d p [ n + m ] [ 0 ] dp[n+m][0] d p [ n + m ] [ 0 ] 。所以要考虑定一个范围,范围外的直接忽略。
把整个集合随机打乱,再限定边界MX,对于 w 和超过MX的情况直接忽略。然后就是01背包。
这必然会有可能取不到正解,但是几率是非常小的。
至于证明还是看题解吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> using namespace std;#define ll long long const int INF = 0x3f3f3f3f ;const ll inf = 0x3f3f3f3f3f3f3f3f ;const int N = 2e6 + 10 ;const ll mod = 1e9 + 7 ;mt19937_64 mt (chrono::steady_clock::now().time_since_epoch().count()) ;int T;int n, m;typedef pair<int , ll>pii;pii a[N]; ll dp[N], tmp[N]; int base = 100000 , MX = 50000 ;int main () { scanf ("%d" , &T); while (T--) { scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= n; i++)scanf ("%d%lld" , &a[i].first, &a[i].second); for (int i = 1 ; i <= m; i++)scanf ("%d%lld" , &a[i + n].first, &a[i + n].second), a[i + n].first *= -1 ; shuffle (a + 1 , a + n + m + 1 , mt); n += m; for (int j = base - MX; j <= base + MX; j++)dp[j] = -inf; dp[base] = 0 ; for (int i = 1 ; i <= n; i++) { for (int j = -MX; j <= MX; j++)tmp[base + j] = dp[base + j]; for (int j = max (-MX, -MX + a[i].first); j <= min (MX, MX + a[i].first); j++) { dp[base + j] = max (tmp[base + j], tmp[base + j - a[i].first] + a[i].second); } } printf ("%lld\n" , dp[base]); } return 0 ; }