HNOI2015解题报告

HNOI2015解题报告

Author: Pengyihao


Day1 T1 亚瑟王


思路

\(f[i][j]\) 表示 \(i\) 一共获得了 \(j\) 次“机会”的概率。

注意这里的“机会”,是指有多少轮中,它前面的所有卡牌,要么在之前的轮中发动过,要么在这一轮中因为运气没有发动。

这里的“机会”不与自己有没有发动相关。所以就算在之前某轮中发动过,这一轮轮到它的时候贡献依然要增加。

于是有 \(f[i][j] = f[i-1][j+1]\cdot (1-(1-p[i-1])^j) + f[i-1][j]\cdot (1-p[i-1])^j\)

\(ans = \sum_{i=1}^n\sum_{j=1}^rf[i][j]\cdot d[i]\cdot (1-(1-p[i])^j)\)


代码

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
#include <bits/stdc++.h>
typedef long long LL;
#define FOR(i, a, b) for (int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (int i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const int MAXN = 230, MAXR = 140;
int T, n, r, d[MAXN];
double p[MAXN], f[MAXN][MAXR];
double power(double x, int y)
{
double ret = 1;
while (y) {
if (y & 1) ret *= x;
x *= x;
y >>= 1;
}
return ret;
}
int main()
{
freopen("arthur.in", "r", stdin);
freopen("arthur.out", "w", stdout);
in(T);
while (T--) {
in(n); in(r);
FOR(i, 1, n) {
scanf("%lf", &p[i]); in(d[i]);
}
memset(f, 0, sizeof f);
f[1][r] = 1;
FOR(i, 2, n) FOR(j, 1, r) {
f[i][j] = 0;
f[i][j] += f[i - 1][j + 1] * (1 - power(1 - p[i - 1], j + 1));
f[i][j] += f[i - 1][j] * power(1 - p[i - 1], j);
}
double ans = 0;
FOR(i, 1, n) FOR(j, 1, r) {
ans += f[i][j] * (1 - power(1 - p[i], j)) * d[i];
}
printf("%.10lf\n", ans);
}
return 0;
}

Day1 T2 接水果


思路

首先,我们求出每个点的dfs序。

然后对于一条x<–>y的路径,如果有路径包含它,那么有

  1. 如果 \(lca(x,y)=x or y\),那么只需要这条路径的一个端点在深度大的点的子树中,另一个端点不在“深度小的点往深度大的点的方向上的第一个点”的子树中即可。

  2. 否则,那么只需要这条路径的两个端点分别在 \(x\)\(y\) 的子树中即可。

我们令这条包含x<–>y的路径的两个端点中dfs序小的点为 \(a\),dfs序大的点为 \(b\)

对于上面两种情况中的任意一种情况,这种情况中 \(a\)\(b\) 所在的子树互不相交,为两个不相交的dfs序区间(不在子树中的情况我们可以转化为在它的补集的区间的情况)。

于是我们就把问题转化为了,每个盘子对应一对(或两对,因为有“不在子树”)区间,然后对于每个水果的两个点,两个区间分别包含其两个点的盘子中,权值第 \(k\) 小的。

我们可以把一维转化成 \(x\) 坐标,另一维转化成 \(y\) 坐标,于是问题就转化为了包含一个点的矩形中第 \(k\) 小的。

于是可以用扫描线+树状数组套主席树解决。


代码

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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#include <bits/stdc++.h>
using std::map;
typedef long long LL;
#define FOR(i, a, b) for (int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (int i = (a), i##_END_ = (b); i >= i##_END_; i--)
#define debug(...) fprintf(stderr, __VA_ARGS__)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const int MAXN = 100010;
const int appear = 0, disappear = 2, query = 1;
int n, p, q, ev_tot;
int cnt, head[MAXN], data[MAXN << 1], nxt[MAXN << 1];
int INDEX, dfn[MAXN], ed[MAXN], depth[MAXN];
int anc[MAXN][18];
int ans[MAXN];
map<int, int>M; int M_point, to[MAXN << 3];
struct Event {
int type, num;
int time, l, r, w;
} ev[MAXN << 3];
namespace segment_tree
{
const int MAXS = MAXN * 250;
int seg_tot = 0, rot[MAXN], sz[MAXS], ch[MAXS][2];
int should_remove[250], should_add[250];
void build(int &now, int l, int r)
{
now = ++seg_tot;
if (l == r) return;
int mid = (l + r) >> 1;
build(ch[now][0], l, mid);
build(ch[now][1], mid + 1, r);
}
void initialize()
{
build(rot[0], 1, M_point);
FOR(i, 1, n) rot[i] = rot[0];
}
void push(int &now, int l, int r, int x, int modi)
{
int tmp = now;
now = ++seg_tot;
sz[now] = sz[tmp];
ch[now][0] = ch[tmp][0];
ch[now][1] = ch[tmp][1];
sz[now] += modi;
if (l == r) return;
int mid = (l + r) >> 1;
if (x <= mid) push(ch[now][0], l, mid, x, modi);
else push(ch[now][1], mid + 1, r, x, modi);
}
void getdown(int &ret, bool t)
{
ret = 0;
FOR(i, 1, should_add[0]) ret += sz[ch[should_add[i]][t]];
FOR(i, 1, should_remove[0]) ret -= sz[ch[should_remove[i]][t]];
}
void godown(bool t)
{
FOR(i, 1, should_add[0]) should_add[i] = ch[should_add[i]][t];
FOR(i, 1, should_remove[0]) should_remove[i] = ch[should_remove[i]][t];
}
void _query(int w, int l, int r, int num)
{
if (l == r) {
ans[num] = l;
return;
}
int ldata; getdown(ldata, 0);
int mid = (l + r) >> 1;
if (ldata >= w) {godown(0); _query(w, l, mid, num);}
else {godown(1); _query(w - ldata, mid + 1, r, num);}
}
void insert(int l, int r, int w)
{
if (l > r) {
debug("WA");
} //This line is for gdb
while (l <= n) {
push(rot[l], 1, M_point, w, 1);
l += l & -l;
}
r++;
while (r <= n) {
push(rot[r], 1, M_point, w, -1);
r += r & -r;
}
}
void remove(int l, int r, int w)
{
while (l <= n) {
push(rot[l], 1, M_point, w, -1);
l += l & -l;
}
r++;
while (r <= n) {
push(rot[r], 1, M_point, w, 1);
r += r & -r;
}
}
void query(int l, int r, int w, int num)
{
l--;
should_remove[0] = should_add[0] = 0;
while (l) {
should_remove[++should_remove[0]] = rot[l];
l -= l & -l;
}
while (r) {
should_add[++should_add[0]] = rot[r];
r -= r & -r;
}
_query(w, 1, M_point, num);
}
}
int lca(int x, int y)
{
if (depth[x] < depth[y]) x ^= y ^= x ^= y;
if (depth[x] != depth[y]) {
int delta = depth[x] - depth[y];
DNF(i, 17, 0) if (delta >> i & 1) x = anc[x][i];
}
if (x == y) return x;
DNF(i, 17, 0) if (anc[x][i] && anc[y][i] && anc[x][i] != anc[y][i]) {
x = anc[x][i]; y = anc[y][i];
}
return anc[x][0];
}
void work(int now)
{
if (ev[now].type == appear) {
segment_tree::insert(ev[now].l, ev[now].r, ev[now].w);
}
else if (ev[now].type == disappear) {
segment_tree::remove(ev[now].l, ev[now].r, ev[now].w);
}
else {
segment_tree::query(1, ev[now].l, ev[now].w, ev[now].num);
}
}
bool cmp(const Event &a, const Event &b)
{
return a.time < b.time || a.time == b.time && a.type < b.type;
}
bool cmp2(const Event &a, const Event &b)
{
return a.w < b.w;
}
void add(int x, int y)
{
nxt[cnt] = head[x]; data[cnt] = y; head[x] = cnt++;
nxt[cnt] = head[y]; data[cnt] = x; head[y] = cnt++;
}
void dfs(int now, int pa)
{
depth[now] = depth[pa] + 1;
anc[now][0] = pa;
for (int i = 1; anc[now][i - 1]; i++)
anc[now][i] = anc[anc[now][i - 1]][i - 1];
dfn[now] = ++INDEX;
for (int i = head[now]; i != -1; i = nxt[i]) {
if (data[i] != pa) dfs(data[i], now);
}
ed[now] = INDEX;
}
int find_fa(int now, int depth)
{
FOR(i, 0, 17) if (depth >> i & 1) now = anc[now][i];
return now;
}
int main()
{
freopen("fruit.in", "r", stdin);
freopen("fruit.out", "w", stdout);
memset(head, -1, sizeof head);
in(n); in(p); in(q);
FOR(i, 1, n - 1) {
int u, v; in(u); in(v); add(u, v);
}
dfs(1, 0);
FOR(i, 1, p) {
int u, v, w;
in(u); in(v); in(w);
int anc = lca(u, v);
if (dfn[u] > dfn[v]) u ^= v ^= u ^= v;
if (u == anc) {
int nodes = find_fa(v, depth[v] - depth[u] - 1);
int ss = dfn[nodes], tt = ed[nodes];
if (ss != 1) {
ev[++ev_tot].time = 1;
ev[ev_tot].l = dfn[v]; ev[ev_tot].r = ed[v];
ev[ev_tot].type = appear; ev[ev_tot].w = w;
ev[++ev_tot].time = ss - 1;
ev[ev_tot].l = dfn[v]; ev[ev_tot].r = ed[v];
ev[ev_tot].type = disappear; ev[ev_tot].w = w;
}
if (tt != n) {
ev[++ev_tot].time = dfn[v];
ev[ev_tot].l = tt + 1; ev[ev_tot].r = n;
ev[ev_tot].type = appear; ev[ev_tot].w = w;
ev[++ev_tot].time = ed[v];
ev[ev_tot].l = tt + 1; ev[ev_tot].r = n;
ev[ev_tot].type = disappear; ev[ev_tot].w = w;
}
}
else {
ev[++ev_tot].time = dfn[u]; ev[ev_tot].l = dfn[v];
ev[ev_tot].r = ed[v]; ev[ev_tot].type = appear; ev[ev_tot].w = w;
ev[++ev_tot].time = ed[u]; ev[ev_tot].l = dfn[v];
ev[ev_tot].r = ed[v]; ev[ev_tot].type = disappear; ev[ev_tot].w = w;
}
}
std::sort(ev + 1, ev + ev_tot + 1, cmp2);
FOR(i, 1, ev_tot) {
if (i != 1 && ev[i].w == ev[i - 1].w) continue;
M[ev[i].w] = ++M_point;
to[M_point] = ev[i].w;
}
FOR(i, 1, ev_tot) {
ev[i].w = M[ev[i].w];
}
FOR(i, 1, q) {
int u, v, w;
in(u); in(v); in(w);
if (dfn[u] > dfn[v]) u ^= v ^= u ^= v;
ev[++ev_tot].time = dfn[u];
ev[ev_tot].l = dfn[v]; ev[ev_tot].w = w;
ev[ev_tot].num = i; ev[ev_tot].type = query;
}
std::sort(ev + 1, ev + ev_tot + 1, cmp);
segment_tree::initialize();
FOR(i, 1, ev_tot) work(i);
FOR(i, 1, q) printf("%d\n", to[ans[i]]);
return 0;
}

Day1 T3 菜肴制作


思路

按照逆序拓扑排序,然后构造字典序最大的解。


代码

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
#include <bits/stdc++.h>
using std::priority_queue;
typedef long long LL;
#define FOR(i, a, b) for (int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (int i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const int MAXN = 100010;
int T, n, m, chose, du[MAXN], order[MAXN];
int cnt, head[MAXN], data[MAXN << 1], nxt[MAXN << 1];
priority_queue<int>q;
void add(int x, int y)
{
nxt[cnt] = head[x]; data[cnt] = y; head[x] = cnt++;
nxt[cnt] = head[y]; data[cnt] = x; head[y] = cnt++;
}
int main()
{
freopen("dishes.in", "r", stdin);
freopen("dishes.out", "w", stdout);
in(T);
while (T--) {
memset(du, 0, sizeof du);
memset(head, -1, sizeof head); cnt = 0;
in(n); in(m); chose = n;
FOR(i, 1, m) {
int x, y; in(x); in(y); add(y, x);
du[x]++;
}
FOR(i, 1, n) {
if (!du[i]) q.push(i);
}
order[0] = 0;
while (!q.empty()) {
int now = q.top(); q.pop(); chose--;
order[++order[0]] = now;
for (int i = head[now]; i != -1; i = nxt[i]) {
du[data[i]]--;
if (!du[data[i]]) {
q.push(data[i]);
}
}
}
if (chose) puts("Impossible!");
else {
DNF(i, n, 1) printf("%d ", order[i]);
putchar(10);
}
}
return 0;
}

Day2 T1 落忆枫音


思路

考虑没有环的情况。

每个点(除了 \(1\) 号点)任选一条入边,则构成一棵脉络树。

所有方案数为 \(\prod_{i=2}^ndegree[i]\)

但是有环,所以答案要减去它。

\(S\)\(y\)\(x\) 的路径。

\(remove=\sum_{S}\prod_{i=2}^n[i\notin S]degree[i]\)

这个可以dp……

\(f[i]\)\(i\)\(x\) 的路径求出的remove。

\(f[i] = \sum_{j->i}f[j]/degree[i]\)


代码

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
#include <bits/stdc++.h>
using std::queue;
typedef long long LL;
#define FOR(i, a, b) for (LL i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (LL i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const LL MOD = 1000000007;
const LL MAXN = 200010;
LL n, m, x, y, ans = 1;
LL f[MAXN], du[MAXN], rdu[MAXN];
LL head[MAXN], data[MAXN], nxt[MAXN], cnt;
LL head1[MAXN], data1[MAXN], nxt1[MAXN], cnt1;
queue<LL>q;
void add(LL x, LL y)
{
nxt[cnt] = head[x]; data[cnt] = y; head[x] = cnt++;
}
void add2(LL x, LL y)
{
nxt1[cnt1] = head1[x]; data1[cnt1] = y; head1[x] = cnt1++;
}
LL power(LL x, LL y)
{
LL ret = 1;
while (y) {
if (y & 1) ret = 1ll * ret * x % MOD;
x = 1ll * x * x % MOD;
y >>= 1;
}
return ret;
}
int main()
{
freopen("maple.in", "r", stdin);
freopen("maple.out", "w", stdout);
memset(head, -1, sizeof head);
memset(head1, -1, sizeof head1);
in(n); in(m); in(x); in(y);
FOR(i, 1, m) {
LL u, v;
in(u); in(v); add(u, v); add2(v, u);
du[v]++; rdu[v]++;
}
FOR(i, 2, n)
if (i != y)
ans = 1ll * ans * du[i] % MOD;
if (x == y || y == 1) {
if (y != 1)
ans = 1ll * ans * du[x] % MOD;
printf("%lld\n", ans);
return 0;
}
LL tmp = ans;
ans = 1ll * ans * (du[y] + 1) % MOD;
FOR(i, 1, n) {
if (!du[i]) q.push(i);
}
while (!q.empty()) {
LL now = q.front(); q.pop();
if (now == y) {
f[now] = tmp;
}
else {
f[now] = 0;
for (LL i = head1[now]; i != -1; i = nxt1[i]) {
f[now] = (f[now] + f[data1[i]]) % MOD;
}
f[now] = 1ll * f[now] * power(rdu[now], MOD - 2) % MOD;
}
for (LL i = head[now]; i != -1; i = nxt[i]) {
du[data[i]]--;
if (!du[data[i]]) {
q.push(data[i]);
}
}
}
printf("%lld\n", (ans - f[x] + MOD) % MOD);
return 0;
}

Day2 T2


这是一道动态点分治的题目。但因为我还没有搞这个专题所以暂且跳过。


Day2 T3


思路

这个题目还是很容易的。

因为所有的 \(x\) 互不相同, 所以可以把大小关系看作是图论中的边。

那么构成的就是一座森林(把相等的点用并差集缩起来)。

然后就可以树形dp辣!

因为有等于,所以我们不知道每棵子树中有多少“块”元素。

我们把相等的元素看成一块,那么一棵子树就是若干个块的大小关系。

我们设第 \(i\) 个点的子树中有 \(j\) 个块的方案数为 \(f[i][j]\)

则两棵子树合并的结果 \(g[i]\)

\[g[i]=\sum_{j=1}^n\sum_{k=1}^n[max(j, k)\leq i\leq j+k]f[son1][j]\cdot f[son2][j]\cdot \binom{i}{j}\binom{j}{j+k-i}\]


代码

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
161
162
#include <bits/stdc++.h>
typedef long long LL;
#define FOR(i, a, b) for (int i = (a), i##_END_ = (b); i <= i##_END_; i++)
#define DNF(i, a, b) for (int i = (a), i##_END_ = (b); i >= i##_END_; i--)
template <typename Tp> void in(Tp &x) {
char ch = getchar(); x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}
template <typename Tp> Tp Min(Tp x, Tp y) {return x < y ? x : y;}
template <typename Tp> Tp Max(Tp x, Tp y) {return x > y ? x : y;}
template <typename Tp> Tp chkmax(Tp &x, Tp y) {return x > y ? x : x=y;}
template <typename Tp> Tp chkmin(Tp &x, Tp y) {return x < y ? x : x=y;}
const int MAXN = 110;
const int MOD = 1000000007;
struct Edge {
int l, r;
} edge[MAXN];
int edge_lenth;
int n, m;
char compare[10];
int du[MAXN], f[MAXN][MAXN];
int head[MAXN], nxt[MAXN], data[MAXN], cnt;
int fa[MAXN];
int find(int x)
{
int tmp = x, pre;
while (tmp != fa[tmp]) tmp = fa[tmp];
while (x != tmp) {
pre = fa[x]; fa[x] = tmp; x = pre;
}
return tmp;
}
void merge(int x, int y)
{
int fx = find(x), fy = find(y);
if (fx != fy) fa[fx] = fy;
}
int g[MAXN], jie[MAXN], ni[MAXN];
int C(int x, int y)
{
if (!y) return 1;
if (x < y) return 0;
return 1ll * jie[x] * ni[y] % MOD * ni[x - y] % MOD;
}
void dfs(int now)
{
for (int i = head[now]; i != -1; i = nxt[i]) dfs(data[i]);
memset(g, 0, sizeof g);
if (head[now] != -1)
FOR(k, 1, n) g[k] = f[data[head[now]]][k];
if (head[now] != -1)
for (int i = nxt[head[now]]; i != -1; i = nxt[i]) {
memset(f[now], 0, sizeof f[now]);
FOR(k, 1, n) FOR(l, 1, n) FOR(j, Max(k, l), Min(n, k + l)) {
f[now][j] = (
f[now][j] +
1ll * g[k] % MOD * f[data[i]][l] % MOD *
C(j, k) % MOD * C(k, k + l - j) % MOD
) % MOD;
}
FOR(k, 1, n) g[k] = f[now][k];
}
if (head[now] == -1) f[now][1] = 1;
else FOR(i, 1, n) f[now][i] = g[i - 1];
}
int power(int x, int y)
{
int ret = 1;
while (y) {
if (y & 1) ret = 1ll * ret * x % MOD;
x = 1ll * x * x % MOD;
y >>= 1;
}
return ret;
}
void add(int x, int y)
{
nxt[cnt] = head[x]; data[cnt] = y; head[x] = cnt++;
}
int main()
{
freopen("pairwise.in", "r", stdin);
freopen("pairwise.out", "w", stdout);
memset(head, -1, sizeof head);
jie[0] = 1;
FOR(i, 1, 100) jie[i] = 1ll * jie[i - 1] * i % MOD;
ni[100] = power(jie[100], MOD - 2);
DNF(i, 99, 1) ni[i] = 1ll * ni[i + 1] * (i + 1) % MOD;
ni[0] = 1;
in(n); in(m);
FOR(i, 1, n) fa[i] = i;
FOR(i, 1, m) {
int x, y;
in(x); scanf("%s", compare); in(y);
if (compare[0] == '<') {
edge[++edge_lenth].l = x; edge[edge_lenth].r = y;
}
else merge(x, y);
}
int ans = 0;
FOR(i, 1, edge_lenth) {
int fx = find(edge[i].l);
int fy = find(edge[i].r);
if (fx == fy) {
puts("0");
return 0;
}
add(fx, fy); du[fy]++;
}
int rot = 0;
FOR(i, 1, n) {
if (!du[find(i)]) rot = find(i);
}
if (!rot) {
puts("0");
return 0;
}
dfs(rot);
FOR(i, 1, n) ans = (ans + f[rot][i]) % MOD;
printf("%d\n", ans);
return 0;
}