SRM566 1000pts
绍一的模拟赛题
【题意】
小Z养了只企鹅,第只企鹅的坐标为,颜色为。出于对企鹅安全的考虑,他决定造栅栏。地面上有个桩,编号,小Z会用一些直的栅栏,每个栅栏连接某两个桩子。对于最后的结果,任意两个栅栏不能交叉。任意一个桩只能连接0个或2个栅栏,栅栏必须围成一些封闭的多边形。由于经费有限,每一个多边形内至少包含一只企鹅。小Z的企鹅有着许多颜色。相同颜色的企鹅必须在同一个多边形内。为了保证所有企鹅的安全,每一只企鹅都必须被包含在某一个多边形内。
各种不合法的情况:
某种合法的情况:
现在小Z想知道自己有多少种合法的不同的造栅栏的方案𝑎𝑛𝑠。
【题解】
用ℎ[𝑙][𝑟][0..1]表示𝑙, 𝑟必须选,点的编号在𝑙, 𝑟之间,最外层的多边形内没有(0)或有(1)企鹅的除了最外层多边形其他多边形均合法的方案数。𝑔[𝑙][𝑟]表示𝑙必须选,点的编号在𝑙, 𝑟之间合法的方案数。𝑓[𝑙][𝑟]表示,点的编号在𝑙, 𝑟之间合法的方案数。当一个栅栏两侧没有相同颜色的企鹅时定义这
个栅栏是有效的。
然后就可以直接区间DP了。考虑计算ℎ[𝑙][𝑟][𝑛𝑜𝑤],枚举中间点𝑛𝑙,当< 𝑛𝑙, 𝑙 >合法时进行转移,分类讨论𝑛𝑜𝑤和三角形(𝑙, 𝑛𝑙, 𝑟)中有没有点的情况,再考虑向量< 𝑙, 𝑛𝑙 >右侧有没有企鹅,没有直接转移,有的话,考虑多边形(𝑙, 𝑙 + 1, 𝑛𝑙 − 1, 𝑛𝑙)内有没有点分类讨论一下即可。考虑计算𝑔[𝑙][𝑟],枚举中间点𝑛𝑟,当< 𝑛𝑟, 𝑙 >合法时进行转移,如果向量< 𝑟, 𝑙 >左侧和向量< 𝑙, 𝑛𝑙 >左侧的企鹅的并集为空𝑔[𝑙][𝑟] += ℎ[𝑙][𝑛𝑟][1],否则如果向量< 𝑛𝑟 + 1, 𝑟 >的企鹅和上面两个的并集为空𝑔[𝑙][𝑟] += ℎ[𝑙][𝑛𝑟][1] * 𝑓[𝑛𝑟 + 1][𝑟]。𝑓[𝑙][𝑟]可以轻松的从𝑔[𝑙][𝑟]转移过来。
【分析】
题解非常地神,直接给出了一个dp,这里从头分析为什么要这么做。
首先很容易确定一条边是否可以选(如果这条边两侧有相同颜色的企鹅,那么不能选)
那么现在问题变成了知道一些边能选不能选,要求选择一些边使得它们围成多个多边形,且每个企鹅都在一个多边形里,每个多边形里都有至少一个企鹅。
问题是在一个环上选出一些边,怎么就变成区间dp了呢?
考虑最朴素的暴力:枚举每条边选不选,再检查是否满足条件。所以第一步,我们考虑枚举一条边,选择任意一个点作为起始点,如下图选择了这个绿色的点
为了不重复,我们枚举这个点沿逆时针方向遇到的第一个有边的点。具体地说,这个点可能有边,可能没有边,如果没有边,我们按逆时针顺序继续枚举下一个点,直到枚举到一个有边的点,标为蓝色。
接下来继续枚举第二的点作为边的另一端,同样的,为了不重复,这个点从开始按顺时针顺序枚举,找到第一个与有边的,由于是从开始第一个有边的点,所以里面的点都不会再有边了,而里点只有它们内部才可能有边,所以这条边只能与里面的点构成多边形了。而这恰好是一个区间,所以可以考虑区间dp。我们现在关心的是什么?是这段区间里,能形成多少种如红线的方案。具体来说就是选了某些边,且都已经作为一个端点,这样就可以在添加了之后合并了,记方案数为(暂时不考虑企鹅的限制),那么此时答案需要加上。
再来看里连边的方案数怎么求,我们发现这又是可以看成一个环,我们又回到了一个和原来的问题一样的问题,可以用同样的方式求解,不妨用表示,这样答案就是。
现在我们考虑怎么求,同样的,我们可以枚举出去的第一条边。
那么现在有两种情况:
1.之间有边,这样的方案数就是。
2.之间没有边,这样的方案数是。
至此,在忽略[每个企鹅都在一个多边形里,每个多边形里都有至少一个企鹅]这个限制的情况下,我们已经可以在解决这个问题了。
我们发现求解h的复杂度是的,而求解f是的,所以考虑优化f的转移。
我们发现
而
对于的部分,两个状态是一样的,所以我们自然不必每次都枚举。
一种思路是记
则
可以理解为一定选的情况下内的方案数。
这样可以在的时间内转移,总时间复杂度.
现在是时候考虑[每个企鹅都在一个多边形里,每个多边形里都有至少一个企鹅]这个限制了。
实际上也很简单,我们只要保证在闭合一个多边形的时候它里面有企鹅,并且在转移的时候保证空出来的图形里没企鹅即可。
这需要在的状态里记录这个未封闭的图形里是否有企鹅,在转移的时候保证S1没有企鹅,在转移h时保证S2没企鹅,在通过连接转移的时候保证S3没有企鹅。
我为什么用了却不用呢?因为在代码里表示连了这条边内的方案数,实际上就是。
现在来回答开头的问题
在环上选点是如何变成了区间dp?
【notice】 mod 100007是会爆int的,一开始注意到了写程序的时候却先忽略了,后来查了半天还以为转移写错了。。
【代码】
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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | #include<bits/stdc++.h> using namespace std; typedef long long LL; const int N = 230, mod = 100007; int cnt[N][N], ok[N][N], col[N]; // cnt[l][r] <l, r> 右侧的企鹅数量 int n, m, r; int get_num( int a[], int n) { int res = m; a[n] = a[0]; for ( int i = 0; i < n; i++) { res -= cnt[a[i]][a[i+1]]; } //assert(res >= 0); return res; } void addit( int &x, int y) { if ((x += y) >= mod) x -= mod; } int f[N][N], h[N][N][2], e[N][N]; int E( int , int ); int F( int l, int r) { if (l >= r || !cnt[l][r]) return 1; int &res = f[l][r]; if (res >= 0) return res; res = 0; for ( int i = l; i+1 < r; i++) { if (cnt[i][r] + cnt[r][l] == m) addit(res, E(i, r)); } return res; } int H( int l, int r, int s) { if (r - l + 1 <= 2) return 0; int &res = h[l][r][s]; if (res >= 0) return res; res = 0; for ( int i = l + 1; i < r; i++) if (ok[l][i]) { int a[] = {l, l + 1, i - 1, i, 0}, b[] = {l, i, r, 0}; if (i - l + 1 >= 3 && get_num(a, 4)) continue ; // 空出的四边形内有企鹅 int sc = get_num(b, 3) > 0; /* 当前三角形里是否有点 */ // 不连<i, r>从h转移 if (s == sc) { addit(res, (LL) F(l + 1, i - 1) * H(i, r, 0) % mod); } if (s) { addit(res, (LL) F(l + 1, i - 1) * H(i, r, 1) % mod); } // 连接<i, r>直接转移 if (ok[i][r] && s == sc) { int c[] = {i, i + 1, r - 1, r, 0}; if (r - i + 1 >= 3 && get_num(c, 4)); else addit(res, (LL) F(l + 1, i - 1) * F(i + 1, r - 1) % mod); } } return res; } #define G(l, r) (ok[l][r] ? H(l, r, 1) : 0) int E( int l, int r) { int &res = e[l][r]; if (res >= 0) return res; res = 0; for ( int i = l + 1; i <= r; i++) if (ok[l][i]) { int a[] = {l, i, i + 1, r, 0}; if (i < r && get_num(a, 4)); else addit(res, (LL) G(l, i) * F(i + 1, r) % mod); } return res; } struct Point { double x, y; Point() {} Point( double x, double y) : x(x), y(y) {} Point operator - ( const Point &rhs) const { return Point(x - rhs.x, y - rhs.y); } } p[N], q[N]; double Cross( const Point &a, const Point &b) { return a.x * b.y - a.y * b.x; } int idx( char c) { if ( 'a' <= c && c <= 'z' ) return c - 'a' ; return c - 'A' + 26; } void input() { scanf ( "%d%d%d" , &n, &m, &r); double pi = acos (-1.0); for ( int i = 0; i < n; i++) { double ang = 2 * pi * i / n; p[i] = Point(r * cos (ang), r * sin (ang)); } char str[9]; for ( int i = 0; i < m; i++) { scanf ( "%s%lf%lf" , str, &q[i].x, &q[i].y); col[i] = idx(str[0]); } } bool OnLeft( const Point &p, const Point &a, const Point &b) { assert ( fabs (Cross(b - a, p - a)) > 1e-3); return Cross(b - a, p - a) > 0; } void init() { LL st[N][N] = {0}; for ( int i = 0; i < n; i++) { for ( int j = 0; j < n; j++) if (i != j) { int &res = cnt[i][j]; for ( int k = 0; k < m; k++) { if (!OnLeft(q[k], p[i], p[j])) { res++; st[i][j] |= 1LL << col[k]; } } } } for ( int i = 0; i < n; i++) { for ( int j = i+1; j < n; j++) { if (st[i][j] & st[j][i]); else ok[i][j] = ok[j][i] = 1; } } } //#define dout(x) cerr << #x << " = " << x << endl int main() { freopen ( "penguin.in" , "r" , stdin); freopen ( "penguin.out" , "w" , stdout); input(), init(); memset (f, -1, sizeof f); memset (h, -1, sizeof h); memset (e, -1, sizeof e); printf ( "%d\n" , F(0, n-1)); return 0; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异